<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Kuro&#39;s Blog</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://kuro.tw/"/>
  <updated>2020-07-07T16:42:20.801Z</updated>
  <id>https://kuro.tw/</id>
  
  <author>
    <name>Kuro Hsu</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>如何在 Vue 3.0 使用 EventBus</title>
    <link href="https://kuro.tw/posts/2020/07/08/%E5%A6%82%E4%BD%95%E5%9C%A8-Vue-3-0-%E4%BD%BF%E7%94%A8-EventBus/"/>
    <id>https://kuro.tw/posts/2020/07/08/如何在-Vue-3-0-使用-EventBus/</id>
    <published>2020-07-07T16:31:07.000Z</published>
    <updated>2020-07-07T16:42:20.801Z</updated>
    
    <content type="html"><![CDATA[<p>上一篇升級筆記提到，雖然 Vue 3.0 移除了事件處理的 <code>$on</code>, <code>$off</code> 以及 <code>$once</code> 等 API，然而我們還是可以借助其他方式來達到 EventBus 的效果。</p><p>這是今天的主角 <a href="https://github.com/developit/mitt" target="_blank" rel="noopener">Mitt</a></p><p><img src="/static/img/mitt.png" alt="mitt"></p><p>Mitt 是一套用來處理事件訂閱/發佈的函式庫，透過 Mitt 我們可以將 Vue 3.0 移除的 <code>$on</code>, <code>$off</code> 等功能再度重現，而更重要的是，<strong>它只有 200 byte</strong> 。 </p><p>你沒看錯，不是 200 kb，是 200 byte。</p><h2 id="安裝"><a href="#安裝" class="headerlink" title="安裝"></a>安裝</h2><p>這裡我們透過 vite 來建立一個簡單的 Vue 3.0 專案:</p><pre><code>$ yarn create vite-app &lt;project-name&gt;$ cd &lt;project-name&gt;$ yarn</code></pre><p>接著為專案加入 mitt:</p><pre><code>$ yarn add mitt</code></pre><p>這樣就完成前置作業了。</p><h2 id="建立-mitt-實體"><a href="#建立-mitt-實體" class="headerlink" title="建立 mitt 實體"></a>建立 mitt 實體</h2><p>mitt 的使用方式與過去透過空的 Vue 實體幾乎一樣。 我們在專案建立 mitt 實體，假設取名叫 <code>bus.js</code>:</p><pre><code class="js">import mitt from &#39;mitt&#39;export default mitt()</code></pre><p>就兩行。</p><p>接著，分別在需要訂閱與接收事件的 Vue Component 裡，加入 bus.js :</p><pre><code class="js">import bus from &#39;./bus&#39;;</code></pre><p>事件接收方，這裡在 <code>created</code> 階段訂閱 <code>plus</code> 自訂事件：</p><pre><code class="js">data() {  return {    count: 0  }},methods: {  plus () {    this.count++;  }},created() {  bus.on(&#39;plus&#39;, this.plus);}</code></pre><p>事件發送方，建立一個 <code>emit</code> method 來向 <code>bus</code> 發送 <code>plus</code> 事件：</p><pre><code class="js">methods: {  emit () {    bus.emit(&#39;plus&#39;);  }}</code></pre><p>以下是簡單的 Vue 3.0 Demo：</p><iframe src="https://codesandbox.io/embed/cocky-hugle-9svi5?fontsize=14&hidenavigation=1&theme=dark" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="cocky-hugle-9svi5" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-autoplay allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;上一篇升級筆記提到，雖然 Vue 3.0 移除了事件處理的 &lt;code&gt;$on&lt;/code&gt;, &lt;code&gt;$off&lt;/code&gt; 以及 &lt;code&gt;$once&lt;/code&gt; 等 API，
然而我們還是可以借助其他方式來達到 EventBus 的效果。&lt;/p&gt;
&lt;p&gt;這是今
      
    
    </summary>
    
      <category term="vue.js" scheme="https://kuro.tw/categories/vue-js/"/>
    
    
      <category term="vue.js" scheme="https://kuro.tw/tags/vue-js/"/>
    
      <category term="vue 3.0" scheme="https://kuro.tw/tags/vue-3-0/"/>
    
  </entry>
  
  <entry>
    <title>Vue 3.0 升級紀錄</title>
    <link href="https://kuro.tw/posts/2020/07/06/Vue-3-0-%E5%8D%87%E7%B4%9A%E7%B4%80%E9%8C%84/"/>
    <id>https://kuro.tw/posts/2020/07/06/Vue-3-0-升級紀錄/</id>
    <published>2020-07-06T04:30:01.000Z</published>
    <updated>2020-07-08T08:58:07.260Z</updated>
    
    <content type="html"><![CDATA[<p>隨著 Vue 3.0 進入到 Beta 階段，而且官方也給出了預計在七月中旬發佈 RC (Release Candidate)，以及正式版即將在八月發佈的明確目標，相信已經有不少朋友都想試試 Vue 3.0 了吧？</p><p>最近也 <del>超前部署</del> 花了一點時間將工作專案從 Vue 2.6 升級上 Vue 3.0 Beta，這篇就簡單做個記錄，以及分享一些升級過程可能會遇到的坑。</p><h2 id="環境說明"><a href="#環境說明" class="headerlink" title="環境說明"></a>環境說明</h2><p>為了確保避免開發環境造成的差異，這裡先列出我目前使用的開發環境：</p><pre><code>$ node -vv14.3.0</code></pre><pre><code>$ yarn -v1.22.4</code></pre><pre><code>$ npm -v6.14.5</code></pre><pre><code>$ vue -V@vue/cli 4.4.6</code></pre><h2 id="起手式"><a href="#起手式" class="headerlink" title="起手式"></a>起手式</h2><p>首先你得要有個 Vue 2.x (當初是以 VueCLI 建置) 的專案，然後在專案目錄下執行</p><pre><code>$ vue add vue-next</code></pre><p>這個時候， Vue CLI 會自動安裝對應的套件。</p><p><img src="/static/img/v3-upgrade/install.png" alt></p><p>看到這個畫面就代表套件升級完成了，升級後可以檢查你的 <code>package.json</code> 檔案以及相關的版本是否有正確更新。</p><h2 id="掛載點的變更"><a href="#掛載點的變更" class="headerlink" title="掛載點的變更"></a>掛載點的變更</h2><p>如果你是用 CLI 預設的 <code>main.js</code> 作為進入點，那麼 Vue CLI 會自動將原本的</p><pre><code class="js">import Vue from &#39;vue&#39;</code></pre><p>改為</p><pre><code class="js">import { createApp } from &#39;vue&#39;;</code></pre><p>原本的掛載點</p><pre><code class="js">new Vue({  router,  store,  render: h =&gt; h(App)}).$mount(&#39;#app&#39;)</code></pre><p>也會自動變成</p><pre><code class="js">createApp(App).use(router).use(store).mount(&#39;#app&#39;)</code></pre><p>的新版語法。</p><p>如果你跟我一樣，使用的是多頁型的專案配置，那麼你會看到</p><pre><code>Cannot find file src/main.js</code></pre><p><img src="/static/img/v3-upgrade/error.png" alt="找不到 main.js"></p><p>的錯誤訊息。</p><p>不過沒關係，相關套件還是會升級，只是你得花點力氣人工手動將每一頁的進入點從 <code>import vue from &#39;vue&#39;</code> 改成 <code>import { createApp } from &#39;vue&#39;</code>，以及 <code>new Vue({...})</code> 改成 <code>createApp(App).mount(&#39;XXX&#39;)</code> 的寫法。</p><p><code>el</code> 屬性不再適用，而掛載時的 <code>mount</code> 也無需再加上 <code>$</code> 了。</p><h2 id="明顯的語法變動"><a href="#明顯的語法變動" class="headerlink" title="明顯的語法變動"></a>明顯的語法變動</h2><p>多數的語法更新在執行 <code>vue-cli-service lint</code> 都會有提示。這裡講一下更新時幾個明顯的語法變動。</p><p>首先是 <code>filter</code> option 被移除，替代方式很簡單就是搬到 <code>methods</code> 來使用，然後將 template 的 pipe 語法改寫。</p><p>事件修飾子的 <code>.native</code> 被移除，更準確的說是不需要了。</p><p>讓我花最多時間改寫的其實是 EventBus 的部分。</p><p>由於 Vue 3.0 移除了事件處理的 <code>$on</code>, <code>$off</code> 以及 <code>$once</code>，使得過去我們透過一個空的 Vue 實體來當作事件訂閱/傳遞的載體 (俗稱 EventBus)，這樣的方式已經不再適用，對於這個問題，我的建議是：直上 Vuex。 你終究要用到 Vuex 的。</p><p><del>當然也有其他方法硬幹，就不在這篇的討論範圍</del></p><p><strong>原本 2.x 的 options API 還是可以用，不用擔心 Vue 3.0 之後都得改成 setup 開頭了</strong>。</p><h2 id="取消-sync-修飾子，新增多組-v-model"><a href="#取消-sync-修飾子，新增多組-v-model" class="headerlink" title="取消 .sync 修飾子，新增多組 v-model"></a>取消 <code>.sync</code> 修飾子，新增多組 <code>v-model</code></h2><p>過去我們透過 <code>prop</code> 與 <code>.sync</code> 來同步上下層 component 狀態 (俗稱雙向綁定) 的方式，現在可以改由多組 <code>v-model</code> 來做到了：</p><pre><code class="html">&lt;my-reg-form  v-model:name=&quot;name&quot;  v-model:email=&quot;email&quot; /&gt;</code></pre><pre><code class="html">&lt;!-- myRegForm.vue --&gt;&lt;template&gt;  &lt;input    :value=&quot;name&quot;    @input=&quot;updateName($event.target.value)&quot; /&gt;  &lt;input    :value=&quot;email&quot;    @input=&quot;updateEmail($event.target.value)&quot; /&gt;&lt;/template&gt;&lt;script&gt;export default {  props: {    name: String,    email: String  },  setup (props, { emit }) {    const updateName = value =&gt; {      emit(&#39;update:name&#39;, value)    }    const updateEmail = value =&gt; {      emit(&#39;update:email&#39;, value)    }    return {       updateName,      updateEmail     }  }}&lt;/script&gt;</code></pre><h2 id="scoped-styles-的改寫"><a href="#scoped-styles-的改寫" class="headerlink" title="scoped styles 的改寫"></a>scoped styles 的改寫</h2><p>CSS 的部分，原本我們為了將樣式穿透至子元素，會使用 <code>/deep/</code> 或 <code>&gt;&gt;&gt;</code> 的語法來處理，未來需要改成 <code>::v-deep()</code> 的形式：</p><pre><code class="css">/* 即將廢棄 */::v-deep .bar {}</code></pre><pre><code class="css">/* Vue 3.0 的寫法 */::v-deep(.bar) {}</code></pre><p>並且新增了一個 <code>::v-global</code> 的 pseudo element，可以用在 <code>style scoped</code> 的範圍下。</p><h2 id="Vuex-的變動"><a href="#Vuex-的變動" class="headerlink" title="Vuex 的變動"></a>Vuex 的變動</h2><p>既然講到 Vuex，那麼 Vuex 升級後有沒有什麼要注意的地方？</p><p>有的，將原本的 </p><pre><code class="js">import Vuex from &#39;vuex&#39;</code></pre><pre><code class="js">export default new Vuex.Store({ ... })</code></pre><p>改成</p><pre><code class="js">import { createStore } from &#39;vuex&#39;</code></pre><pre><code class="js">export default createStore({ ... })</code></pre><p>就這樣，沒了。</p><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><p>最後是 webpack 或 <code>vue.config.js</code> 的 <code>alias</code> 需要改成 <code>vue$: &#39;vue/dist/vue.esm-bundler.js&#39;</code>，否則 CLI 會告訴你他找不到 Vue，一直要你重裝。</p><p>目前升級 Vue 3.0 的開發體驗相當好，像是大家都很熟悉的 composition api、直接在 component 使用 <code>v-model</code>，以及新增的 async-component 與 <code>&lt;Suspense&gt;</code> 等等，都是非常實用的功能。</p><p>缺點是截至目前還沒有 devtool 可以用，得憑經驗通靈。 聽說 RC 出來的時候就會有了 XD</p><h2 id="工商服務"><a href="#工商服務" class="headerlink" title="工商服務"></a>工商服務</h2><p>這篇只是記錄一下升級遇到的幾個問題。</p><p>如果你對 Vue 3.0 新增的部分也有興趣，剛好我在五倍紅寶石八月份有開設一門 <a href="http://5xruby.tw/talks/vue-js/" target="_blank" rel="noopener">Vue.js 與 Vuex 前端開發實戰課程</a>，<strong>教材已經更新至 3.0 (當然 2.x 也適用)</strong>，歡迎你來參加。</p><p><img width="360" src="https://5xruby.tw/assets/images/talks/cover/vue-js-61eaa1c7.jpg"></p><p>參考資料與相關連結:</p><ol><li><a href="https://github.com/vuejs/rfcs/issues/183/" target="_blank" rel="noopener">https://github.com/vuejs/rfcs/issues/183/</a></li><li><a href="https://github.com/vuejs/vue-next" target="_blank" rel="noopener">https://github.com/vuejs/vue-next</a></li><li><a href="https://github.com/vuejs/vue-cli-plugin-vue-next" target="_blank" rel="noopener">https://github.com/vuejs/vue-cli-plugin-vue-next</a></li><li><a href="https://github.com/vuejs/rfcs/tree/master/active-rfcs" target="_blank" rel="noopener">https://github.com/vuejs/rfcs/tree/master/active-rfcs</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;隨著 Vue 3.0 進入到 Beta 階段，而且官方也給出了預計在七月中旬發佈 RC (Release Candidate)，
以及正式版即將在八月發佈的明確目標，相信已經有不少朋友都想試試 Vue 3.0 了吧？&lt;/p&gt;
&lt;p&gt;最近也 &lt;del&gt;超前部署&lt;/del&gt; 花
      
    
    </summary>
    
      <category term="vue.js" scheme="https://kuro.tw/categories/vue-js/"/>
    
    
      <category term="vue.js" scheme="https://kuro.tw/tags/vue-js/"/>
    
      <category term="vue 3.0" scheme="https://kuro.tw/tags/vue-3-0/"/>
    
  </entry>
  
  <entry>
    <title>從國師級的專案看開發者的過失</title>
    <link href="https://kuro.tw/posts/2020/06/09/%E5%BE%9E%E5%9C%8B%E5%B8%AB%E7%B4%9A%E7%9A%84%E5%B0%88%E6%A1%88%E7%9C%8B%E9%96%8B%E7%99%BC%E8%80%85%E7%9A%84%E9%81%8E%E5%A4%B1/"/>
    <id>https://kuro.tw/posts/2020/06/09/從國師級的專案看開發者的過失/</id>
    <published>2020-06-09T09:32:26.000Z</published>
    <updated>2020-06-10T03:04:12.003Z</updated>
    
    <content type="html"><![CDATA[<p>在技術圈打滾了一段時間，各種鄙視鏈看得也不少，時不時就會看到有人問，ＯＯ 跟 ＸＸ 相比那個比較強啊？別誤會，我們今天不談什麼框架大亂鬥也不講語言鄙視鏈。</p><p>今天在臉書社團看到國師有個新開發的星盤網站，不看還好一看驚為天人。</p><p><img src="https://imgur.com/Yb472Ad.jpg" alt="國師級的專案"></p><p>居然是用 Vue.js 寫的，而且開發者模式 (development mode) 還開著。Vue.js 提供的開發者模式，其實是方便開發者在「編寫程式的時候」除錯，所以不會對專案的程式碼進行打包與優化。</p><p>那，一個上線的專案開著開發者模式，這有什麼關係嗎？</p><p>確實在正常情況下開著開發者模式頂多就是執行起來效能比較差，馬照跑，舞照跳，<del>50年不變</del> 程式還是會動，頂多就是寫在前端的程式碼讓大家參觀，沒有什麼大不了。</p><p>但是對開發習慣極差的開發者來說，上線前的打包動作，除了對專案的各種優化外，其實是前端建構工具保護你的最後一道關卡。</p><p><img src="https://imgur.com/ABPZmUj.jpg" alt="一言不合就開源，全站大放送"></p><hr><p>有些開發者為了一時方便測試，會將測試的資訊寫在程式碼註解裡無可厚非，但是當網站在開發者模式的時候，任何人都可以從瀏覽器裡看到這些測試資訊的註解，而且不用裝任何外掛就看得到。</p><p><img src="https://imgur.com/523FYYj.jpg" alt="應該是測試登入的使用者資訊，實際上還真的能用"></p><hr><p>換言之，我們只要按照上面所寫的，將測試資訊寫入 <code>localStorage</code> 就可以模擬開發者的身分，超開心！如果說，這是個收費的網站，是否代表所有人都可以不用註冊免費算到飽？</p><p>連 swagger 都不藏私，佛心！</p><p><img src="https://imgur.com/p8Uwnty.jpg" alt="想要我的星盤嗎？ 想要的話可以全部給你，去找吧！ 我把所有的星盤都放在那裡了。"></p><hr><p>虧錢事小，最嚴重的問題在於，<strong>任何人都可以利用這個漏洞來隨意取得其他人的個資，甚至連登入都不需要</strong>。</p><p><img src="https://i.imgur.com/DS2DMez.jpg" alt="攤開在 HTML 裡的 CSS 有 4944 個 !important 堪稱本世紀的奇蹟"></p><hr><p>千算萬算，唐老師你有算到你的網站正冒著極大風險之中嗎？</p><p>前端後端都不行，做網站不是只有畫面好看就好，開發者自己都沒有格調就不要怪人家看你沒有。開發工具原本應該是帶給開發者方便，但不代表就可以拿著隨便用，任何一個有職業良知的開發者都不應該犯這種低級錯誤。</p><p>技術圈各種鄙視鏈，大家早已見怪不怪，可以說是哪裡有人，哪裡就有鄙視鏈。</p><p>但技術真有高下？ 我說<strong>再好的技術都擋不住開發習慣極差的開發者</strong>。</p><p><img src="https://imgur.com/7cQj5fL.jpg" alt="是南北拳的問題還是寫程式的人的問題呢"></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;在技術圈打滾了一段時間，各種鄙視鏈看得也不少，時不時就會看到有人問，ＯＯ 跟 ＸＸ 相比那個比較強啊？
別誤會，我們今天不談什麼框架大亂鬥也不講語言鄙視鏈。&lt;/p&gt;
&lt;p&gt;今天在臉書社團看到國師有個新開發的星盤網站，不看還好一看驚為天人。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;
      
    
    </summary>
    
      <category term="個人喇賽" scheme="https://kuro.tw/categories/%E5%80%8B%E4%BA%BA%E5%96%87%E8%B3%BD/"/>
    
    
      <category term="vue.js" scheme="https://kuro.tw/tags/vue-js/"/>
    
      <category term="前端開發" scheme="https://kuro.tw/tags/%E5%89%8D%E7%AB%AF%E9%96%8B%E7%99%BC/"/>
    
  </entry>
  
  <entry>
    <title>初探 Vue 3.0 Function-based API</title>
    <link href="https://kuro.tw/posts/2019/08/06/%E5%88%9D%E6%8E%A2-Vue-3-0-Function-based-API/"/>
    <id>https://kuro.tw/posts/2019/08/06/初探-Vue-3-0-Function-based-API/</id>
    <published>2019-08-06T05:10:45.000Z</published>
    <updated>2019-12-09T06:40:49.980Z</updated>
    
    <content type="html"><![CDATA[<p>從去年年底開始， Vue 3.0 的消息就不斷開始流傳，無論是官方或非官方的消息都有。 尤其在今年 (2019) 六月在 Vue.js 的作者尤雨溪在 VueConf 2019 上海的演講 (<a href="https://docs.google.com/presentation/d/1giA0aF6b5kljg42NUYVTB5bMxxgAc2iRVPVDo2sTirI/edit" target="_blank" rel="noopener">投影片</a> / <a href="https://www.youtube.com/watch?v=AnoZheeH0Nk" target="_blank" rel="noopener">錄影</a>)，讓我們終於可以一窺堂奧，感受 Vue 3.0 一個比較完整的樣貌。</p><p>而其中改變最多，也是爭議最大的一個新特性，當屬 <strong>Vue 3.0 Function-based API</strong> 了。<a id="more"></a></p><h2 id="增強對-TypeScript-的支援"><a href="#增強對-TypeScript-的支援" class="headerlink" title="增強對 TypeScript 的支援"></a>增強對 TypeScript 的支援</h2><p>如果一直以來都有追蹤 Vue.js 3.0 相關的消息，你應該知道 3.0 其中有個很重要的目標是增強對 TypeScript 的支援。</p><p>為什麼這麼強調對 TypeScript 的支援? 這就要說到早期 Vue.js 的設計，其實是沒有做類型系統 (type-system) 的設計，而 Vue.js 的 Component 本質上就是一個「描述元件各種屬性 (options) 的物件」，這樣的好處是開發可以很容易上手，如果有寫過 Vue.js 的朋友應該很清楚這點。</p><p>但相對地缺點就是沒有了這些設計，Vue.js 要做到與工具鍊的組合以及 virtual dom、模板與 js 邏輯的映射優化，就不免處處遇到限制。</p><p>而當年為了弭平這個問題，官方選擇的解法是 Facebook 提供的 Flow 來提供 Vue.js 的類型檢查，而不是 MS 的 TypeScript。 後來的結局就是 Flow 爛尾了，TypeScript 整個生態圈越來越完整... 套句尤大的說法就是：呵呵，真香。</p><h2 id="被廢棄的-Class-API"><a href="#被廢棄的-Class-API" class="headerlink" title="被廢棄的 Class API"></a>被廢棄的 Class API</h2><p>前面說到， Vue.js 為了與 TypeScript 有更好的支援與整合，在 2.x 時期的做法是透過 <a href="https://github.com/vuejs/vue-class-component" target="_blank" rel="noopener">vue-class-component</a> 來整合，這樣的做法雖然可以做到型別系統，但在底層仍繞了很多彎，踩了不少坑。</p><p>而 Vue.js 3.0 在新版本考慮到與原有 API 的相容性、在 TypeScript 與非 TypeScript 開發者間的平衡 (看看 Angular 2+，不用 TS 無法開發)、瀏覽器對原生 class 的支援程度、透過 class API 開發所需要的 <code>Class fields</code>, <code>Decorators</code> 等等提案還不穩定 (stage &lt; 4) 等因素，因此最後決定放棄 Vue 3.0 對 Class API 的路線。</p><h2 id="Vue-3-0-Function-based-API"><a href="#Vue-3-0-Function-based-API" class="headerlink" title="Vue 3.0 Function-based API"></a>Vue 3.0 Function-based API</h2><p>放棄 Class API 的路線之後，取而代之的是 Function-based API。</p><p>Function-based API，又稱 composition functions：</p><pre><code class="js">const App = {  setup() {    // data    const count = value(0)    // computed    const plusOne = computed(() =&gt; count.value + 1)    // method    const increment = () =&gt; { count.value++ }    // watch    watch(() =&gt; count.value * 2, v =&gt; console.log(v))    // lifecycle    onMounted(() =&gt; console.log(&#39;mounted!&#39;))    // 暴露给模版或渲染函数    return { count }  }}</code></pre><p>可以看到，雖然捨棄了過去在 <code>mixins</code> 的直覺，改採 <code>setup()</code> 的 function 來包裝所有的共用邏輯，但以往在 component 裡的所有 options 都可以一一找到對應。</p><p>簡單來說，它的精神是以「組合代替繼承」來實作對元件的複用。</p><p>在過去我們可能會透過 <code>mixins</code> 、 <code>HOC</code> (Higher-order Components) 或是 <code>Renderless Components</code> 與 <code>slot</code> 來處理邏輯的抽象與複用，但他們各自都有各自產生的問題，當你用多了之後，就容易產生像是 <strong>Namespace 衝突、 Prop 的資料來源難以追蹤，或是要產生額外的 component 實體造成性能的消耗</strong> 等。</p><p>而 Function-based API 則順利地解決了上面這些可能會遇到的問題。</p><p>關於 Vue 3.0 Function-based API 的細節，大家可以參考這份 <a href="https://github.com/vuejs/rfcs/blob/function-apis/active-rfcs/0000-function-api.md" target="_blank" rel="noopener">RFC</a> (Request for Comments, 意見徵求稿) ，英文不太好的朋友，這裡有 <a href="https://zhuanlan.zhihu.com/p/68477600" target="_blank" rel="noopener">中文</a> 的版本，不過要注意因為是 RFC 階段，所以<strong>隨時都還有可能會更新，而翻譯版本通常不會同步更新</strong>。</p><hr><p>雖然目前 Vue 3.0 還在 RFC 的階段，但如果你也想先試試 Function-based API 的威力的話，這裡有個為了 2.x 的版本增強的 plugin: <a href="https://github.com/vuejs/vue-function-api" target="_blank" rel="noopener">Vue Function API</a></p><p>使用方式就跟過去使用其他 Vue 相關 Plugin 一樣，如果你是使用 cli 或自建 vue-loader：</p><p>npm:</p><pre><code>$ npm install vue-function-api --save</code></pre><p>yarn:</p><pre><code>$ yarn add vue-function-api</code></pre><p>然後再透過 <code>import</code> 與 <code>Vue.use</code> 的方式載入即可：</p><pre><code class="js">import Vue from &#39;vue&#39;import { plugin } from &#39;vue-function-api&#39;Vue.use(plugin)</code></pre><p>詳細的內容 API 文件裡都有，有興趣的朋友可以自行參閱: <a href="https://github.com/vuejs/vue-function-api/blob/master/README.md" target="_blank" rel="noopener">https://github.com/vuejs/vue-function-api/blob/master/README.md</a></p><hr><p>同時在八月份的社群聚會，我也改寫了文件上的範例，做了 V2 與 Function-based API 的簡單 demo，提供給大家做比較：</p><p><img src="/static/img/vue-function-demo.png" alt></p><h4 id="傳統-Vue-2-x-的做法"><a href="#傳統-Vue-2-x-的做法" class="headerlink" title="傳統 Vue 2.x 的做法"></a>傳統 Vue 2.x 的做法</h4><pre><code class="js">export default {  props: {    title: String  },  data() {    return {      todo: &quot;&quot;,      items: [&quot;Vue&quot;, &quot;is&quot;, &quot;Awesome&quot;]    };  },  methods: {    // Add: Click Handler Function    add() {      if (this.todo) {        this.items.push(this.todo);        this.todo = &quot;&quot;;      }    },    // Remove: Click Handler Function    remove(item) {      this.items = this.items.filter(v =&gt; v !== item);    }  },  // mounted hook  mounted() {    console.log(`mounted ! ${ this.title }`);  }};</code></pre><h4 id="Function-based-API-的做法"><a href="#Function-based-API-的做法" class="headerlink" title="Function-based API 的做法"></a>Function-based API 的做法</h4><pre><code class="js">import { value, onMounted } from &quot;vue-function-api&quot;;export default {  props: {    title: String  },  setup(props, context) {    // Reactive value-based variables    const todo = value(&quot;&quot;);    const items = value([&quot;Vue&quot;, &quot;is&quot;, &quot;Awesome&quot;]);    // Add: Click Handler Function    const add = () =&gt; {      if (todo.value) {        items.value.push(todo.value);        todo.value = &quot;&quot;;      }    };    // Remove: Click Handler Function    const remove = item =&gt; {      items.value = items.value.filter(v =&gt; v !== item);    };    // mounted hook    onMounted( () =&gt; {      console.log(`onMounted ! ${ props.title }`);    });    return {      todo,      items,      add,      remove    };  }};</code></pre><p>可以看到新的寫法將邏輯統一包裝在 <code>setup()</code> function 中，對比過去以物件的形式來說，不管是在共用邏輯的管理 (現代的關注點分離：以「元件、功能」為區分，該放一起的就放一起，不像過去以 HTML / CSS / JS 來區分)，在程式打包、壓縮以及 Tree-shaking 來說都有著更大的優勢。</p><p>完整原始檔: <a href="https://github.com/kurotanshi/vue-function-api-demo" target="_blank" rel="noopener">https://github.com/kurotanshi/vue-function-api-demo</a> </p><p>同時也將活動簡報分享出來， <a href="https://speakerdeck.com/kurotanshi/chu-tan-vue-3-dot-0-function-api" target="_blank" rel="noopener">https://speakerdeck.com/kurotanshi/chu-tan-vue-3-dot-0-function-api</a></p><p><img src="/static/img/chu-tan-vue-3-dot-0-function-api.png" alt></p><p>對 Vue 3.0 Function-based API 有興趣的朋友不妨自行試試囉。</p><hr><p>最後要提醒各位，Vue 3.0 雖然導入了 Function-based API 的新特性，但是原本的語法不會因此被廢棄</p><p>原本的語法不會被廢棄!</p><p>原本的語法不會被廢棄!</p><p>原本的語法不會被廢棄!</p><p>很重要所以講三次。</p><h2 id="依然是工商服務"><a href="#依然是工商服務" class="headerlink" title="依然是工商服務"></a>依然是工商服務</h2><p>這是我最近在五倍紅寶石開設的 VueJS 入門課程，如果你對 VueJS 有興趣，卻總是不得其門而入，歡迎點此 <a href="https://5xruby.tw/talks/vue-js/" target="_blank" rel="noopener">https://5xruby.tw/talks/vue-js/</a> 報名參加。</p><p><img src="https://5xruby.tw/assets/images/talks/cover/vue-js-61eaa1c7.jpg"></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;從去年年底開始， Vue 3.0 的消息就不斷開始流傳，無論是官方或非官方的消息都有。 
尤其在今年 (2019) 六月在 Vue.js 的作者尤雨溪在 VueConf 2019 上海的演講 
(&lt;a href=&quot;https://docs.google.com/presentation/d/1giA0aF6b5kljg42NUYVTB5bMxxgAc2iRVPVDo2sTirI/edit&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;投影片&lt;/a&gt; / &lt;a href=&quot;https://www.youtube.com/watch?v=AnoZheeH0Nk&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;錄影&lt;/a&gt;)，讓我們終於可以一窺堂奧，感受 Vue 3.0 一個比較完整的樣貌。&lt;/p&gt;
&lt;p&gt;而其中改變最多，也是爭議最大的一個新特性，當屬 &lt;strong&gt;Vue 3.0 Function-based API&lt;/strong&gt; 了。
    
    </summary>
    
      <category term="vue.js" scheme="https://kuro.tw/categories/vue-js/"/>
    
    
      <category term="vue.js" scheme="https://kuro.tw/tags/vue-js/"/>
    
      <category term="vue.js 3.0" scheme="https://kuro.tw/tags/vue-js-3-0/"/>
    
      <category term="Function-based API" scheme="https://kuro.tw/tags/Function-based-API/"/>
    
      <category term="TypeScript" scheme="https://kuro.tw/tags/TypeScript/"/>
    
  </entry>
  
  <entry>
    <title>JS 冷知識: 你所不知道的 void</title>
    <link href="https://kuro.tw/posts/2019/08/04/JS-%E5%86%B7%E7%9F%A5%E8%AD%98-%E4%BD%A0%E6%89%80%E4%B8%8D%E7%9F%A5%E9%81%93%E7%9A%84-void/"/>
    <id>https://kuro.tw/posts/2019/08/04/JS-冷知識-你所不知道的-void/</id>
    <published>2019-08-04T15:30:24.000Z</published>
    <updated>2019-12-06T11:26:39.181Z</updated>
    
    <content type="html"><![CDATA[<p><img src="/static/img/void.jpg"></p><p>在 JavaScript 裡， <code>void</code> 運算子可以說是存在感最薄弱的一個了，這個運算子只有一個功能，就是接收任意的運算式或值，然後回傳 <code>undefined</code>。 (你沒看錯，就是 <code>undefined</code>)</p><a id="more"></a><p>用法與 <code>typeof</code> 一樣，可以在後面加上小括號 <code>()</code> 或是直接加上某個值：</p><pre><code class="js">void 0;   // undefinedvoid(0);  // undefined</code></pre><p>毫無反應，就是回傳 <code>undefined</code>。</p><p>而你可能在某些地方看過 <code>void(0)</code> 這樣的程式碼，尤其是在 <code>&lt;a&gt;</code> 標籤的 <code>href</code> 屬性： <code>&lt;a href=&quot;javascript:void(0)&quot;&gt;...&lt;/a&gt;</code></p><p>那麼這樣一個神奇的 <code>void</code> 運算子到底是用來做什麼的呢？</p><p>根據 ECMAScript 262 的規範中提到：</p><blockquote><p>The void Operator</p><p>The production UnaryExpression : void UnaryExpression is evaluated as follows:</p><ul><li>Let expr be the result of evaluating UnaryExpression.</li><li>Call GetValue(expr).</li><li>Return undefined.</li></ul><p>NOTE: GetValue must be called even though its value is not used because it may have observable side-effects.</p></blockquote><p>雖然無論 <code>void</code> 後面的值是什麼，它都會回傳 <code>undefined</code> 的結果，但是有個需要特別注意的地方是，這個 <code>void</code> 後面接的運算式是「會被執行的」(有沒有加上括號無所謂)。</p><pre><code class="js">// console 主控台會印出 &quot;HI&quot;，然後回傳 undefinedvoid (console.log(&#39;HI!&#39;));</code></pre><p>而在 <code>href</code> 裡面加上 <code>javascript:void(0)</code> 代表著這是一個無目標的死連結。</p><p>當然自從我們有了 HTML / JS 分離的概念後，多數開發者會採用在 click event 裡面加上 <code>e.preventDefault()</code> 的方式來阻擋預設事件。</p><p>但是早期 (指 IE9 以前) 瀏覽器的事件綁定就至少有 W3C DOM 標準的 <code>element.addEventListener</code> 與 IE 獨有的 <code>element.attachEvent</code> 要處理，在過去，與其要自己特別處理不同事件的阻擋，還不如直接下 <code>&lt;a href=&quot;javascript:void(0)&quot;&gt;...&lt;/a&gt;</code> 來得快速，而且還可以再下 <code>onclick=&quot;...&quot;</code> 來處理後續對應的行為。</p><p>不過此時此刻多數瀏覽器都已遵循 DOM 標準，現在不太需要這樣處理了，只是我們多少還會看到 <code>href=&quot;javascript:void(0)&quot;</code> 這樣的做法。</p><p>另外，要是 <code>void</code> 後面再加上一個 IIFE，即使這個 function 是有名字的：</p><pre><code class="js">void function saySomething (msg) {  console.log(msg);} (&#39;Hello&#39;);</code></pre><p>因為 <code>void</code> 後面的指令是會執行的，所以此時 console 會直接印出 <code>&quot;Hello&quot;</code> 的字樣。</p><p>若你再嘗試去呼叫 <code>saySomething</code> 這個函式：</p><pre><code class="js">// console.log 會立即印出 &quot;Hello&quot;void function saySomething (msg) {  console.log(msg);} (&#39;Hello&#39;);// 則會出現 Uncaught ReferenceError: saySomething is not defined 的錯誤訊息saySomething(&#39;Hi&#39;);</code></pre><p>像這樣，雖然 <code>void</code> 的使用情境不多，但有些開發者會習慣在此類一次性呼叫的 IIFE 前面加上 <code>void</code> 來增加程式碼的可讀性。</p><hr><p>最後再分享一個有趣的事，如果你曾使用過 <a href="http://livescript.net" target="_blank" rel="noopener">LiveScript</a> 開發，你可能會看過編譯出來的 JavaScript 程式有很多 <code>void 8</code>，像這樣：</p><pre><code class="livescript">x = if truthy then success!</code></pre><p>上面這段程式編譯後會得到：</p><pre><code class="js">var x;x = truthy ? success() : void 8;</code></pre><p>根據前面的說明我們都知道 <code>void 8</code> 最後會得到 <code>undefined</code> 的結果，而這個 <code>void 8</code> 有沒有什麼特別用意呢？</p><p>其實沒有，根據 LiveScript 的核心 <a href="https://github.com/satyr/coco" target="_blank" rel="noopener">coco</a> 裡的<a href="https://github.com/satyr/coco/wiki/trivia" target="_blank" rel="noopener">解釋</a>:</p><blockquote><p>void 8 - the number 8 was chosen because it is a Chinese lucky number.</p></blockquote><p>使用 <code>8</code> 單純只是它在中文代表是個幸運的數字 (音近似「發」) ，換成其他數字都可以，重點只是取它回傳 <code>undefined</code> 這樣的作用。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;img src=&quot;/static/img/void.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;在 JavaScript 裡， &lt;code&gt;void&lt;/code&gt; 運算子可以說是存在感最薄弱的一個了，這個運算子只有一個功能，就是接收任意的運算式或值，然後回傳 &lt;code&gt;undefined&lt;/code&gt;。 (你沒看錯，就是 &lt;code&gt;undefined&lt;/code&gt;)&lt;/p&gt;
    
    </summary>
    
      <category term="名為 JavaScript 的黑魔法" scheme="https://kuro.tw/categories/%E5%90%8D%E7%82%BA-JavaScript-%E7%9A%84%E9%BB%91%E9%AD%94%E6%B3%95/"/>
    
    
      <category term="javascript" scheme="https://kuro.tw/tags/javascript/"/>
    
      <category term="void" scheme="https://kuro.tw/tags/void/"/>
    
      <category term="重新認識 JavaScript" scheme="https://kuro.tw/tags/%E9%87%8D%E6%96%B0%E8%AA%8D%E8%AD%98-JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>談談前端框架</title>
    <link href="https://kuro.tw/posts/2019/07/31/%E8%AB%87%E8%AB%87%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6/"/>
    <id>https://kuro.tw/posts/2019/07/31/談談前端框架/</id>
    <published>2019-07-31T13:00:00.000Z</published>
    <updated>2019-12-06T11:22:43.467Z</updated>
    
    <content type="html"><![CDATA[<p>由於瀏覽器與 JavaScript 標準的進展，近年來前端領域可以說是突飛猛進，各種前端框架/函式庫也如雨後春筍般紛紛出現。</p><p>常常有人問，「怎麼選擇前端框架」你會怎麼回答？ </p><p><img src="/static/img/allin.jpeg" alt="小孩子才做選擇，我全都要"></p><p>既然有這麼多人願意開發這些工具，無論理由是什麼，就代表需求確實存在，問題需要被解決。</p><a id="more"></a><hr><p>有句話是這樣說的：「每 18 至 24 個月，前端都會難一倍」</p><p>是變得簡單還是越來越難，我認爲要看團隊本身對於技術的掌握度以及專案的規模來決定。如果要我用一句話來解釋，我會說前端框架/工具庫的發展方向，實際上是「<strong>在簡單的專案使用會變得複雜，在複雜專案的開發變得單純</strong>」。</p><p>談這個問題前，讓我們先來看看網頁技術的發展。 我相信，唯有了解過往的時空背景，我們才能看清現在與未來技術發展的脈絡。</p><h2 id="網頁：毫無反應，就只是個文檔"><a href="#網頁：毫無反應，就只是個文檔" class="headerlink" title="網頁：毫無反應，就只是個文檔"></a>網頁：毫無反應，就只是個文檔</h2><p>  HTML 最早是由網際網路 (World Wide Web) 之父 Tim Berners-Lee 所發明，而在 1991 年成為公開的文件規範。  而該規範並不是 HTML 1.0 ，而是稱為 HTML Tags ；當時的 HTML 主要是用來表達資料，支援的標籤也不多。</p><p>  隨著時代的演進，雖然 HTML、JavaScript 與 CSS 都建立了標準，但是在網頁的領域依然 (相較於現在) 還是以後端語言為主。</p><p>  我認為主要原因有兩個：其一是網路速度的限制，二是終端設備效能的低落。</p><p>  網路速度我想不用我多做解釋了，回想一下就知道，3G、4G 的普及化也是最近沒幾年的事。  當網路的傳遞速度不夠快，我們也很難期待讓網頁可以成為一個成熟的應用平台對吧。</p><p>  再來是終端設備的效能，這包含 PC 端上的效能、手機端的效能，甚至是瀏覽器的效能限制。  當硬體的效能還沒有達到一定水準，瀏覽器廠商也就沒有動力更新產品，而作為網頁應用的唯一載體，瀏覽器發展的停滯也就直接影響了整個前端技術的發展。  所以回顧過去，你會發現 ECMAScript 的標準更曾停滯了將近十年之久。</p><p>  一直到最近幾年，這些限制才紛紛有了突破。</p><h2 id="網頁如何變成應用程式"><a href="#網頁如何變成應用程式" class="headerlink" title="網頁如何變成應用程式"></a>網頁如何變成應用程式</h2><p>  早期的網頁幾乎都是以靜態網頁為主，所有資料直接從 Server 端輸出，幾乎不需要做運算，就是單純的 HTML。  就算有後端語言，頂多也就是負責表單的處理，GET / POST 這些大家熟到不能再熟的東西。</p><p>  後來過了幾年，有了「動態網頁程式」的語言 (如 PHP、ASP 等) 開始由後端程式語言負責處理頁面邏輯，  加上資料庫系統的成熟，使得原本無法紀錄狀態的網頁，可以利用資料庫來記錄狀態及資料。</p><p>  既然提到網頁應用程式，就一定會提到 2004 年 Gmail 的誕生，Gmail 的橫空出世，同時也將 AJAX 這門技術發揚光大。</p><p>  說實話，在當時 AJAX 它不能算是一個新技術，從字面上來說，「Asynchronous JavaScript and XML」 - 非同步的 JavaScript 與 XML，  就可以看出其實是好幾種技術的組合，而現今主流的 AJAX 甚至連 XML 都看不見了，多以 JSON 格式替代。 (所以應該改稱 <strong>AJAJ</strong> ?)</p><p>  AJAX 的發展，讓開發者們發現原來不是只能透過瀏覽器發送 Request ，用 JavaScript 也可以向伺服器發送請求。  而這門技術事實上在 1998 年由微軟的 Outlook Web Access 小組所開發，叫做 XMLHTTP，但直到 2004 年 Gamil 的出現這門技術才廣為人們所知。</p><p>  網頁開發者 (當時還未有前後端之分) 開始體會到，原來資料不只是透過單純後端操作，而是可以放在瀏覽器端來管理。 因此，網頁是可以用來開發「應用程式」的。</p><h2 id="前端的任務"><a href="#前端的任務" class="headerlink" title="前端的任務"></a>前端的任務</h2><p>  讓我們把主題拉回前端。 你認為前端的工作應該是什麼？</p><p>  撰寫 HTML? CSS? JavaScript? 或許也有人會說要有美感、有設計的 sense、能跟後端溝通... </p><p>  以上這些都沒錯。</p><p>  但前端最關鍵的任務我會說是<strong>將資料與狀態視覺化</strong>。</p><p>  視覺化? 別開玩笑了，我知道講到視覺化很多人可能會想到 D3.js、EChart、highcharts 等等繪製圖表的工具，不過我們這裡要講的不是這個。</p><p>  早期的前後端分工大多是這樣的，那時前端的工作基本上被戲稱為是「頁面仔」，簡單來說就是負責切版，寫寫 HTML、CSS，偶而再加點 JavaScript 特效。 然後就把頁面交給後端去套版，向後端拉拉資料顯示頁面就完工了。</p><p>  那麼廣義來說，將 HTML 的原始碼透過瀏覽器轉化成使用者看得到的畫面，能不能算是「視覺化」的一種？</p><p>  用 Bootstrap modal 、Lightbox 顯示對話框，即使在程式碼裡面只有 <code>true</code> 或 <code>false</code>，但這個狀態決定對話框什麼時候出現，什麼時候消失，是不是也能算是視覺化的一種？</p><p>  更深一層，當我們把網頁作為應用程式時，實際上我們就是在操作資料與狀態本身，那些標籤、元件都只是表皮，實際上我們就是把這些資料通通都「視覺化」，只是與傳統後端相比，前端除了要維護原本從後端取得的商業邏輯的狀態以外，還得多維護 UI 元件顯示邏輯的狀態。</p><p>  你可能聽過 React 陣營提出了一個公式： <code>view = f(state);</code> 講的就是這件事。</p><p>  當我們定義好畫面元素的顯示與操作邏輯後，剩下要關注的就只有狀態本身。 現代的幾個主流 MVVM 框架基本上也都遵循這個原則。</p><h2 id="前端框架、函式庫的崛起"><a href="#前端框架、函式庫的崛起" class="headerlink" title="前端框架、函式庫的崛起"></a>前端框架、函式庫的崛起</h2><p>  在理解了前端工程的主要任務與技術發展的時空背景後，總算要進入主題了。</p><p>  作為前端開發控制網頁的手段，開發者第一個要面臨的問題就是 DOM 的操作。</p><p>  在那個時期，各家瀏覽器對 JavaScript 的各種實作可以說是五花八門，其中最典型的例子就是網頁的「事件處理」：</p><p>  在早期的 IE (其實也沒有那麼早，泛指 IE9 以前)，事件綁定就分為 W3C DOM 標準的 <code>addEventListener()</code> 以及 IE 獨有的 <code>attachEvent()</code>。</p><pre><code class="js">  function addEvent(ev, elem, func) {    if (elem.addEventListener) {      // W3C DOM      elem.addEventListener(ev, func, false);    }    else if (elem.attachEvent) {      // IE DOM      elem.attachEvent(&quot;on&quot; + ev, func);    }    else {      // No much to do      elem[ev] = func;    }  }</code></pre><p>  更不用說一個要加 <code>on</code>，另一個不用，如果同個事件綁定多個處理器，甚至連執行先後順序也不一樣。</p><p>  而透過 jQuery 處理事件只需要一個 <code>$(...).bind()</code> 或 <code>$(...).on()</code> 就足夠。</p><p>  除了解決跨瀏覽器的問題之外，另一個痛點就是對於 DOM 的控制。</p><p>  假設今天想要用 Javascript 讀取網頁中某個單選按鈕 (radio button)，在沒有 <code>document.querySelector</code> 可以用的時代，來看看要怎麼做：</p><pre><code class="js">  var checkedValue = null;  var elements = document.getElementsByTagName(&#39;input&#39;);  for (var n = 0; n &lt; elements.length; n++) {    if (elements[n].type === &quot;radio&quot; &amp;&amp;        elements[n].name == &quot;radioField&quot; &amp;&amp;        elements[n].checked){      checkedValue = elements[n].value;    }  }</code></pre><p>  像這樣，我們必須先找出所有的 <code>&lt;input&gt;</code> 標籤，然後透過 <code>for</code> 一個一個檢查它的 <code>type</code> 以及 <code>name</code>，最後才把對應的 <code>value</code> 取出。</p><p>  透過 jQuery 來查找，我們甚至只需要一行：</p><pre><code class="js">  var checkValue = $(&#39;[name=&quot;radioField&quot;]:checked&#39;).val();</code></pre><p>  就可以做到一樣的效果。 這也是為什麼 jQuery 在那個時代可以大放異彩，即便是現在也還有人在使用。 當然在這裡並不是要教各位怎麼使用 jQuery，而是想要表達一個觀念：「框架或工具都是因應<strong>某個需要被解決的問題</strong>而生」。</p><p>  時代在進步，技術也在進步，在不同的時間點，就會有不同的問題需要被解決，技術總是圍繞著需求而生。</p><h2 id="MV-框架的出現與開發思維的轉變"><a href="#MV-框架的出現與開發思維的轉變" class="headerlink" title="MV* 框架的出現與開發思維的轉變"></a>MV* 框架的出現與開發思維的轉變</h2><p>  在 jQuery 解決了 DOM 操作的問題後，首先面臨的就是狀態管理的問題。</p><p>  我還記得我剛入行的時候，那時候沒有什麼狀態管理的觀念，後端把資料吐在 HTML 上，我們就透過 JavaScript 直接把資料從 DOM 取出，處理完再寫回 DOM 上。 像這樣把 DOM 當做是資料結構來處理資料的方式顯然有很大的問題，當某個 DOM 被刪除的時候，是否意味著這個資料將永久消失再也拿不到了？</p><p>  所以，當時多數人會做的事情，就是把資料往某個全域變數丟。 優點就是彈性大，缺點就是彈性太大，什麼人都可以存取。</p><p>  全域變數有什麼不好？ 以學習的角度來說，在新手階段其實無需太在意別人怎麼說，當你遇到某個需求，你在第一時間想到的解法也許就是最棒的解法，做就對了。 如果這是個重要的專案，一定會有人盯著你做，換個角度來說，如果可以放手讓你自己來，那表示這個專案的重要性其實也還好。</p><p>  直到你發現這個問題用這個解法會帶來其他難以解決的問題，那就表示專案的複雜度夠高了，那麼參考前人的作法，你會發現那些所謂的框架、工具、設計模式其實都是水到渠成的結果，這個時候你也升級了。</p><p>  當然這個觀點放到技術選型也是一樣。</p><hr><p>  從早期的「義大利麵式程式碼」(Spaghetti code) 把所有的東西通通往 HTML 頁面塞，到了後來有人提倡「關注點分離」，  將 HTML、CSS 及 JavaScript 拆開來，這是表現層級上的關注點分離。</p><p>  當專案的架構越來越大，人們開始把「關注點」從表現層移到了架構層面，於是又分成了「資料層」、「表現層」以及「邏輯層」，也就是所謂的 MVC 概念。</p><p>  <img src="https://ithelp.ithome.com.tw/upload/images/20171231/20065504gPGQJLFA10.png" alt="mvc示意圖"></p><p>  當瀏覽器開啟 View 的時候，View 會向 Controller 提出請求。  Controller 處理完商業邏輯的部分之後，要求 Model 端改變狀態，然後 Model 再將資料反映到 View 上。</p><p>  早期像是 Backbone.js，算是我第一個接觸的前端 MVC 框架。 </p><p>  它搭配了 Underscore.js 及 jQuery，開發者可以透過 RESTful interface 來跟它的 Model 及 Collection 結合操作，  當資料被改變了之後，就會觸發 View 去更新，作出對應的變化，比較可惜的是當時還沒有實作 data-binding 的部分。</p><p>  真正稱上集大成可以算是 EmberJS、AngularJS (2013) 以及 Knockout.js 時期，這幾套前端框架各有所長。  但最驚豔的部分是 View 和 Model 可以直接來做 binding，這對過去很習慣直接以操作 DOM 為開發手段的我們，其實是個相當大的突破。   除此之外，像是 directive、template 以及 routing 的整合，也為開發者帶來相當大的便利。</p><p>  前面提到，以早期著名的前端 MVC 框架 Backbone.js 來說，使用者可以透過 DOM 事件對 View 發送指令，再由 View 要求 Model 改變狀態。   但同時使用者也可以透過操作 URL 來對 Controller 做操作，再由 Controller 去改變 View。</p><p>  這個時候你會發現前端 MVC 與後端 MVC 有著一個很大的差異： 前端的 MVC 著重在事件流程，而後端的重點在於資料流。</p><p>  後端 MVC 的 View 就相當於是前端的全部了：  <img src="https://ithelp.ithome.com.tw/upload/images/20171231/200655049gO5tuxY1m.jpg" alt="前端與後端的 MVC">  圖片來源：<a href="http://slides.com/evenchange4/2014-ntuim-prepconf#/2/3" target="_blank" rel="noopener">http://slides.com/evenchange4/2014-ntuim-prepconf#/2/3</a></p><p>  當前後端分離後，後端幾乎不需要去處理前端 View 的部分。  相對地，後端的 V 變成前端的 M，那些原本放在後端處理畫面、邏輯的部分，有很大程度地往前端移動。</p><p>  比起把原本後端的路由管理接回來用，此時前端更注重的是資料與狀態的管理，以及它們如何跟畫面的整合與同步。</p><p>  於是前端框架慢慢發展成 MVVM 模式。</p><p>  像早期的 Ember、Angular 採用資料的雙向綁定作法，當 View 操作 (DOM event) 變動，透過 ViewModel 去操作 Model (JavaScript Object)，反過來也是。</p><p>  後來崛起的 React、Vue 也都延續採用 Single Source of Truth (SSOT) 的準則，使得資料流更容易被追蹤，架構也更好維護。</p><p>  一般來說，後端的 Controller 是指 URL 與 Http Method 的部分，而前端可以操作 URL (hashTag / history-api 的 pushState) 來處理路由，以及透過 DOM Event 去對 View 做操作。 </p><p>  所以現代主流前端框架不會直接限制自己為 MVC 框架，而是被稱作 MV* (MVC/MVP/MVVM...) 的框架。</p><h2 id="MVVM-之後？"><a href="#MVVM-之後？" class="headerlink" title="MVVM 之後？"></a>MVVM 之後？</h2><p>  在眾多 M-V-VM 框架解決了狀態與畫面同步的問題後，下一個課題是網頁應用的元件化。   應用的元件化之後，代表同樣的東西可以重複拿來使用，觀念雖好，但也帶來了新的問題：元件與元件之間的狀態傳遞。</p><p>  寫過 react 的朋友應該都知道，早期 react 官方其實不管這一塊，所以整個社群才會又出現 flux、reflux 到 redux 等等的解決方案，</p><p>  如果不使用這些解決方案來管理狀態的話，當元件需要做狀態的傳遞，就得一口氣往上傳到最上層，然後再一步一步往下丟。</p><p>  通俗點的比喻就像是大家庭裡面兩個堂兄弟講悄悄話，結果搞到阿公阿祖全家人都知道。</p><p>  這個問題在 Vue 等其他框架其實也有，所以後來提出了 <code>eventbus</code> 的解決法。</p><p>  <img src="/static/img/eventbus.png" alt="Event Bus"></p><p>  寫過 Vue 的朋友都知道，這個 eventbus 跟前面講到的「全域變數」的概念相當類似，差別只在於它不像全域變數那樣自由，多了一層存取的限制。</p><p>  如果我們說從前元件父子關係的狀態傳遞是樹狀結構，那麼 eventbus 就是網狀結構。 就好比某個元件想跟另一個元件溝通，先跟總機聯絡，這個總機會幫你轉到正確的目標，這個 eventbus 就是總機的角色。 在網站應用還在中小型規模的時候，用這樣的方式來管理狀態也是很合適的。</p><p>  而當我們的專案規模成長到一定程度時，大到 eventbus 難以控制，那麼這個時候再考慮使用像是 Vuex、 Redux、MobX 甚至是 rxjs 之類的解決方案也不遲，就看你的需求複雜度與應用場景來決定採用什麼。</p><p>  <img src="/static/img/vuex.png" alt="Vuex"></p><h2 id="結論"><a href="#結論" class="headerlink" title="結論"></a>結論</h2><p>現代前端框架這麼多，我相信剛入行的朋友多數都會有選擇恐慌症，覺得我非得找到大家都說那個最強最好用的前端框架，再投入開發。</p><p>總覺得學了一個框架，結果這個框架在社群不是主流派別，豈不是虧大。 但綜觀前端技術的發展歷史，沿著時間軌跡看下來，其實不難發現，那些新的技術、新的工具都是圍繞著問題而生。現有的工具不夠好用，綁手綁腳寫起來不夠爽，於是有人就自幹了一套新的解法，如果這個解法確實可以解決大家也遇到的問題，那麼竄紅起來也就只是時間的問題。</p><p>而那些所謂「過時」的技術或工具並不是說現在就不能用，而是隨著時間過去，開發者的想法提升了、技術的標準進步了，我們有更好的做法，過去的舊技術適合的場景變少了，於是慢慢退場。 上個世代的工具解決了上個世代的問題，然後不管是思維也好，技術也好，都在繼續向前推進。 就好像以前 jQuery 的 Deferred、現在 ES 標準的 promise、async/await... 過去的工具也許在未來會換個形式被納入變成標準也說不定。</p><p>所以 「為什麼需要前端框架？」「怎麼選擇前端框架？」</p><p>專案有它本身的複雜度，當然工具以及其生態圈也是。 如果 jQuery 甚至原生的 JavaScript 就可以解決你的需求，那麼也許你並不需要前端框架。 </p><p>如果是學習的角度，我認為你可以隨便挑一套，先從小型、無長期維護需求的活動專案，或是自己的 side project 開始試著實作看看，寫得下去就恭喜你，要是不合胃口就換一套，反正工具這麼多，總會試出一套適合自己的。</p><h2 id="工商服務"><a href="#工商服務" class="headerlink" title="工商服務"></a>工商服務</h2><p>這是我最近在五倍紅寶石開設的 VueJS 入門課程，如果你對 VueJS 有興趣，卻總是不得其門而入，歡迎點此 <a href="https://5xruby.tw/talks/vue-js/" target="_blank" rel="noopener">https://5xruby.tw/talks/vue-js/</a> 報名參加。</p><p><img src="https://5xruby.tw/assets/images/talks/cover/vue-js-61eaa1c7.jpg"></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;由於瀏覽器與 JavaScript 標準的進展，近年來前端領域可以說是突飛猛進，各種前端框架/函式庫也如雨後春筍般紛紛出現。&lt;/p&gt;
&lt;p&gt;常常有人問，「怎麼選擇前端框架」你會怎麼回答？ &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/static/img/allin.jpeg&quot; alt=&quot;小孩子才做選擇，我全都要&quot;&gt;&lt;/p&gt;
&lt;p&gt;既然有這麼多人願意開發這些工具，無論理由是什麼，就代表需求確實存在，問題需要被解決。&lt;/p&gt;
    
    </summary>
    
      <category term="vue.js" scheme="https://kuro.tw/categories/vue-js/"/>
    
    
      <category term="vue.js" scheme="https://kuro.tw/tags/vue-js/"/>
    
      <category term="react" scheme="https://kuro.tw/tags/react/"/>
    
      <category term="angular" scheme="https://kuro.tw/tags/angular/"/>
    
      <category term="jquery" scheme="https://kuro.tw/tags/jquery/"/>
    
      <category term="前端框架" scheme="https://kuro.tw/tags/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6/"/>
    
  </entry>
  
  <entry>
    <title>談談 JavaScript 的 setTimeout 與 setInterval</title>
    <link href="https://kuro.tw/posts/2019/02/23/%E8%AB%87%E8%AB%87-JavaScript-%E7%9A%84-setTimeout-%E8%88%87-setInterval/"/>
    <id>https://kuro.tw/posts/2019/02/23/談談-JavaScript-的-setTimeout-與-setInterval/</id>
    <published>2019-02-23T10:12:47.000Z</published>
    <updated>2019-12-06T11:25:58.534Z</updated>
    
    <content type="html"><![CDATA[<p>相信接觸過 JavaScript 的朋友對於要透過 JavaScript 來控制時間或是實作一個計時器，一定都會想到 <code>setTimeout()</code> / <code>setInterval()</code>。 </p><p>但你知道透過 <code>setTimeout()</code> 或 <code>setInterval()</code> 來計算時間，不僅每個人執行的結果不同，而且<strong>誤差還可能相當大</strong>嗎？在正式進入主題前，先來簡單介紹一下 <code>setTimeout()</code> 與 <code>setInterval()</code> 。</p><a id="more"></a><h2 id="setTimeout-與-setInterval"><a href="#setTimeout-與-setInterval" class="headerlink" title="setTimeout() 與 setInterval()"></a><code>setTimeout()</code> 與 <code>setInterval()</code></h2><p>根據 <a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout" target="_blank" rel="noopener">MDN 定義</a> <code>setTimeout()</code> 的作用是在延遲了某段時間 (單位為毫秒) 之後，才去執行「一次」指定的程式碼，並且會回傳一個獨立的 timer ID：</p><pre><code class="javascript">var timeoutID = scope.setTimeout(function[, delay, param1, param2, ...]);var timeoutID = scope.setTimeout(function[, delay]);var timeoutID = scope.setTimeout(code[, delay]);</code></pre><p>如：</p><pre><code class="javascript">var timeoutID = window.setTimeout(( () =&gt; console.log(&quot;Hello!&quot;) ), 1000);</code></pre><p>而 <code>setInterval()</code> 則是固定延遲了某段時間之後，才去執行對應的程式碼，然後「不斷循環」。 當然也會回傳一個獨立的 timer ID：</p><pre><code class="javascript">var intervalID = scope.setInterval(func, delay[, param1, param2, ...]);var intervalID = scope.setInterval(code, delay);</code></pre><p>如：</p><pre><code class="javascript">var timeoutID = window.setInterval(( () =&gt; console.log(&quot;Hello!&quot;) ), 1000);</code></pre><p>兩者的不同之處是 <code>setTimeout()</code> 只會執行一次就結束，而 <code>setInterval()</code> 則是會在間隔固定的時間不斷重複。</p><p>由於在瀏覽器的環境宿主物件為 <code>window</code>，所以 <code>setTimeout()</code> 與 <code>setInterval()</code> 的完整語法自然就是我們所熟悉的 <code>window.setTimeout()</code> 與 <code>window.setInterval</code> 了。</p><p>值得注意的是，雖然 <code>setTimeout()</code> 這些 timer 方法不在 ECMAScript 的規格內，而是屬於 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/WindowOrWorkerGlobalScope" target="_blank" rel="noopener">wndow 物件</a>的一部份，不過多數瀏覽器與 <a href="https://github.com/nodejs/node/blob/master/lib/timers.js" target="_blank" rel="noopener">Node.js 也都有實作這些 timer 方法</a>，根據環境不同實作也多少有些差異，像是瀏覽器的 <code>setTimeout</code> / <code>setInterval</code> 回傳的是 Number，而 Node.js 回傳的是 Object。</p><h2 id="如何取消-setTimeout-與-setInterval"><a href="#如何取消-setTimeout-與-setInterval" class="headerlink" title="如何取消 setTimeout() 與 setInterval()"></a>如何取消 <code>setTimeout()</code> 與 <code>setInterval()</code></h2><p>先說 <code>setInterval()</code>，根據前面的範例：</p><pre><code class="javascript">var timeoutID = window.setInterval(( () =&gt; console.log(&quot;Hello!&quot;) ), 1000);</code></pre><p><code>setInterval()</code> 一啟動之後就會在固定的間隔時間不斷執行，那麼如果想要停下來，該怎麼處理呢？</p><p>這時候就需要用到 <code>clearInterval()</code> 來取消 <code>setInterval()</code>。</p><p>上面說過，當我們呼叫 <code>setTimeout()</code> 與 <code>setInterval()</code> 的時候，它們會回傳一個獨立的 timer ID，這個 ID 就是當我們想要取消<code>setTimeout()</code> 與 <code>setInterval()</code> 的時候作為識別的數字：</p><pre><code class="javascript">var timeoutID = window.setInterval(( () =&gt; console.log(&quot;Hello!&quot;) ), 1000);window.clearInterval(timeoutID);</code></pre><p>當程式執行到 <code>clearInterval()</code> 就會取消 <code>setInterval()</code> 了。</p><p>另外，與 <code>setTimeout()</code> 對應的就是 <code>clearTimeout()</code>，用法完全一樣：</p><pre><code class="javascript">var timeoutID = window.setTimeout(( () =&gt; console.log(&quot;Hello!&quot;) ), 1000);window.clearTimeout(timeoutID);</code></pre><p>兩者不同的是，因為 <code>setTimeout()</code> 只會執行一次，所以 <code>clearTimeout()</code> 只會在 <code>setTimeout()</code> 指定的時間未到時才會有效果，若 <code>setTimeout()</code> 的 callback function 已經被執行，那就等同是多餘的了。</p><hr><p>上面是多數人熟知的 <code>setTimeout()</code> 與 <code>setInterval()</code>。 </p><p>可能有些人不知道 <code>setTimeout()</code> 的第一個參數是可以指定「字串」的，如：</p><pre><code class="javascript">window.setTimeout(&#39;console.log(&quot;Hello!&quot;)&#39;, 1000);</code></pre><p>上面這段程式的執行結果與前面的範例是一樣的，但由於內部隱含 <code>eval</code> 的轉換，所以執行上效能會比非字串的版本差一些。</p><p>但要是不小心打成這樣：</p><pre><code class="javascript">window.setTimeout(console.log(&quot;Hello!&quot;), 1000);</code></pre><p>這時雖然 console 主控台仍會印出 2 ，但並不會等待 1000ms，而是會馬上執行。</p><p>這是為什麼呢？</p><p>因為 <code>setTimeout()</code> 會先判斷第一個參數是否為 「function」，如果不是，則會嘗試將它當作字串處理。 換句話說，會將 <code>console.log(&quot;Hello!&quot;)</code> 執行後的回傳值轉為字串，......欸，沒有回傳值，那就是 <code>undefined</code>，於是</p><pre><code class="javascript">window.setTimeout(console.log(&quot;Hello!&quot;), 1000);</code></pre><p>實際上是</p><pre><code class="javascript">window.setTimeout(&quot;undefined&quot;, 1000);</code></pre><p>於是 1000ms 到了就什麼事都沒發生。</p><p>而 &quot;Hello&quot; 則是在呼叫 <code>console.log()</code> 的當下就會被印出。</p><h2 id="當-setTimeout-遇到迴圈-IIFE"><a href="#當-setTimeout-遇到迴圈-IIFE" class="headerlink" title="當 setTimeout 遇到迴圈: IIFE"></a>當 setTimeout 遇到迴圈: IIFE</h2><p>通常談到 <code>setTimeout()</code> 的時候，就不可避免會提到這個經典的範例， 題目是這樣的：</p><blockquote><p>「假設想透過迴圈 + <code>setTimeout()</code> 來實作，在五秒鐘之內，每秒鐘依序透過 <code>console.log()</code> 印出： 0 1 2 3 4 。」</p></blockquote><p>很多 JavaScript 的初學者可能會很直覺地寫下這樣的程式碼：</p><pre><code class="javascript">// 假設想透過迴圈 + setTimeout 來做到// 每秒鐘將 i 的值 console 出來for( var i = 0; i &lt; 5; i++ ) {  window.setTimeout(function() {    console.log(i);  }, 1000);}</code></pre><p>但是上面這段 code 執行的結果是， <code>console.log()</code> 會在「一秒鐘之後」同時印出「五次 5」。</p><p>這是為什麼呢？ 由於 JavaScript 切分變數有效範圍 (scope) 的最小單位為 function，所以當 1000ms 過去，變數 <code>i</code> 的值早已是 <code>for</code> 迴圈更新完畢的 <code>i</code> ，而不是迴圈內當下的那個 <code>i</code> 。</p><p>所以我們會改用這樣的寫法來隔離變數作用域：</p><pre><code class="javascript">for( var i = 0; i &lt; 5; i++ ) {  // 為了凸顯差異，我們將傳入後的參數改名為 x  // 當然由於 scope 的不同，要繼續在內部沿用 i 這個變數名也是可以的。  (function(x){    window.setTimeout(function() {      console.log(x);    }, 1000 * x);  })(i);}</code></pre><p>像這樣的做法，通常稱它為 <strong>IIFE (Immediately Invoked Function Expression)</strong>， <del>一用就丟</del> 立刻被呼叫、執行的 function 表達式。</p><p>或者，也可以改用 <code>let</code> 提供的 Block Scope 特性：</p><pre><code class="javascript">for( let i = 0; i &lt; 5; i++ ) {  window.setTimeout(function() {    console.log(i);  }, 1000 * i);}</code></pre><p>就可以避開這個問題。</p><h2 id="從-setTimeout-看同步、非同步-與-Event-Loop"><a href="#從-setTimeout-看同步、非同步-與-Event-Loop" class="headerlink" title="從 setTimeout 看同步、非同步 與 Event Loop"></a>從 setTimeout 看同步、非同步 與 Event Loop</h2><p>大多數的朋友可能都知道，JavaScript 是單一執行緒的程式語言，這代表著 JavaScript 在同一個時間點只能做一件事。 這時你可能會有疑問，既然是單一執行緒，那為什麼又有「同步」(Synchronous) 與「非同步」 (Asynchronous) 之分呢？ 這兩者又有什麼區別？ 而 JavaScript 這門語言究竟是「同步」還是「非同步」呢？</p><p>兩者容易搞混我覺得很大的原因出在 Synchronous 被翻譯成「同步」，光看字面上「同步」二字就可能把它想成是「所有動作同時進行」，但實際上卻<strong>剛好相反</strong>。</p><p>不囉唆，先來個範例：</p><pre><code class="javascript">console.log(&#39;start&#39;);(function () {  console.log(&#39;call function&#39;)  window.setTimeout(function () {    console.log(&#39;setTimeout&#39;);  }, 1000);})();console.log(&#39;end&#39;);</code></pre><p>各位可以猜猜看，上面這段 code 執行的結果為何？</p><p>相信聰明如你一定知道， console 主控台輸出的結果為：</p><pre><code class="txt">startcall functionendsetTimeout</code></pre><p>先印出 <code>start</code> 、 <code>call function</code> 然後因為 <code>setTimeout()</code> 等待一秒的關係，所以印了 <code>end</code> 後，最後才是 <code>setTimeout</code>。</p><p>那麼，如果我們將 <code>setTimeout()</code> 的時間參數設定為 <code>0</code> 是否就代表會立刻執行呢：</p><pre><code class="javascript">console.log(&#39;start&#39;);(function () {  console.log(&#39;call function&#39;)  window.setTimeout(function () {    console.log(&#39;setTimeout&#39;);  }, 0);})();console.log(&#39;end&#39;);</code></pre><p>.<br>.<br>.<br>.<br>.<br>.<br></p><p>答案仍然是</p><pre><code class="txt">startcall functionendsetTimeout</code></pre><p>這時候我想應該開始有人會感到疑惑了。 </p><p>為什麼 <code>setTimeout()</code> 的時間參數明明已經設定為 <code>0</code>，卻還是最後才執行呢？ 這正是因為 JavaScript 是單一執行緒所帶來的特性造成。</p><p>如果我們將程式要做的所有事情當作是一件一件的任務來看，那麼「一次只能做一件事」的單一執行緒就代表著<strong>任務與任務之間需要排隊</strong>，前一個任務完成才會接著執行下一個任務。 如果目前執行的動作需要很長一段時間，那麼勢必會卡住下一個任務的執行。</p><p>另外，如果這門語言強烈遵守「一次只能做一件事」的原則，那麼在前面所提到的：</p><pre><code class="javascript">console.log(&#39;start&#39;);(function () {  console.log(&#39;call function&#39;)  window.setTimeout(function () {    console.log(&#39;setTimeout&#39;);  }, 0);})();console.log(&#39;end&#39;);</code></pre><p>裡頭的 <code>console.log(&#39;end&#39;)</code> 就應該要等待 <code>setTimeout()</code> 印出後才會執行，但是看結果顯然沒有。 而且，要是因為加上了 <code>setTimeout()</code> 就得延遲後面的所有任務， 在等待 timer 的過程中並沒有做任何事，也白白浪費了時間，這門程式語言的效率肯定極差無比。</p><p>幸好當初 JavaScript 的設計者沒有這麼做，而是將所有等待被執行的任務分成了兩種：「同步」(Synchronous) 與「非同步」 (Asynchronous) 。</p><p>主要執行緒指的是正在排隊等待要執行的任務，也就是圖中的 Stack：</p><p><img src="https://developer.mozilla.org/files/4617/default.svg" alt="Stack, heap, queue"><a href="https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/EventLoop" target="_blank" rel="noopener">圖片來源: MDN - Event Loop</a></p><p>Stack 裡面的任務有同步、也有些非同步事件，像是本文不斷提到的 <code>setTimeout()</code> 或者 <code>Ajax</code> 等等非同步的事件。</p><p>JavaScript 的執行緒會逐一執行 Stack 內的任務，當碰上了非同步事件時，為了不讓程式被這些需要等待的任務卡著，就會繼續執行後續的動作。 而當這些非同步事件的 callback function 被呼叫時，就會將 callback function 的任務丟到 Event Queue 當中，並等待目前 Stack 的任務都已經完成後，再繼續逐一執行 Event Queue 的任務。</p><p>所以再次回到範例：</p><pre><code class="javascript">console.log(&#39;start&#39;);(function () {  console.log(&#39;call function&#39;)  window.setTimeout(function () {    console.log(&#39;setTimeout&#39;);  }, 0);})();console.log(&#39;end&#39;);</code></pre><p>即便我們在 <code>setTimeout()</code> 的等待時間設定為 <code>0</code>， 因為 JavaScript 會先將其擱置到 Queue 當中，等待 Stack 的任務完成後，再回來執行 <code>setTimeout()</code> 內的 callback function。 這也就是為什麼範例中 <code>setTimeout</code> 總是會比 <code>end</code> 還要晚印出的原因了。</p><h2 id="不僅貌似忠良的男子不可相信，-setInterval-提供的時間也不可信"><a href="#不僅貌似忠良的男子不可相信，-setInterval-提供的時間也不可信" class="headerlink" title="不僅貌似忠良的男子不可相信， setInterval 提供的時間也不可信"></a>不僅貌似忠良的男子不可相信， setInterval 提供的時間也不可信</h2><p>跟著文章看到這裡，我想你應該已經知道用 <code>setTimeout()</code> 與 <code>setInterval()</code> 拿來計算時間會有什麼問題了。</p><p>雖然 JavaScript 有著非同步事件的特色，但仍是依循單一執行緒的規則運作。 換句話說，當我們在主要執行緒內工作的時間太久，就勢必會延遲 Queue Callback 的執行。</p><p>首先要建立一個概念，就是任何操作都會有時間成本：</p><pre><code class="javascript">var startTime = new Date().getTime();window.setTimeout(function(){  var endTime = new Date().getTime();  console.log(&#39;Time elapsed: &#39; + (endTime - startTime) + &#39; ms&#39;); }, 1000);</code></pre><p>像上面這段程式，雖然 <code>setTimeout()</code> 被指定在 1000ms 後執行，但這段 code 的執行結果為</p><pre><code class="txt">Time elapsed: 1005 ms  (依環境可能會有些微誤差)</code></pre><p>實際執行可以發現仍然大約有 2 ~ 5 ms 的時間誤差，雖然人體感受不明顯。</p><p>但如果中間再加上一些操作</p><pre><code class="javascript">var startTime = new Date().getTime();var count = 0;// 用來模擬延遲 Queue 的動作window.setInterval(function(){  var i = 0;  while(i++ &lt; 999999) { };}, 0);window.setInterval(function(){  var endTime = new Date().getTime();  count++;  console.log(&#39;Time elapsed: &#39; + (endTime - (startTime + count * 1000)) + &#39; ms&#39;); }, 1000);</code></pre><p>實際執行的輸出為 (依執行環境有所不同) ：</p><pre><code class="txt">&quot;Time elapsed: 108 ms&quot;&quot;Time elapsed: 198 ms&quot;&quot;Time elapsed: 123 ms&quot;&quot;Time elapsed: 145 ms&quot;&quot;Time elapsed: 179 ms&quot;&quot;Time elapsed: 121 ms&quot;&quot;Time elapsed: 160 ms&quot;(下略)</code></pre><p>像上面這樣，即便每次執行都只有 100 ~ 200 ms 誤差，但久而久之 <code>setInterval()</code> 帶來的誤差就會漸漸擴大。</p><h2 id="精準校時？"><a href="#精準校時？" class="headerlink" title="精準校時？"></a>精準校時？</h2><p>先說結論，別傻了孩子，不管你怎麼對 <code>setTimeout()</code> 或 <code>setInterval()</code> 做優化，誤差永遠都會存在。但好消息是我們可以透過一些手法來降低誤差，像是一開始就先計算 client 的時間與 server 端的時間差，並定期與 server 端做時間同步修正，使誤差維持在最小可接受範圍內等。</p><p>如果是要執行動畫，則建議用 <a href="https://developer.mozilla.org/zh-TW/docs/Web/API/Window.requestAnimationFrame" target="_blank" rel="noopener">requestAnimationFrame</a> 來取代 <code>setTimeout()</code> 或 <code>setInterval()</code>。  比起傳統的 <code>setTimeout()</code> 或 <code>setInterval()</code> ， <code>requestAnimationFrame</code> 的執行效能會好上許多。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;相信接觸過 JavaScript 的朋友對於要透過 JavaScript 來控制時間或是實作一個計時器，一定都會想到 &lt;code&gt;setTimeout()&lt;/code&gt; / &lt;code&gt;setInterval()&lt;/code&gt;。 &lt;/p&gt;
&lt;p&gt;但你知道透過 &lt;code&gt;setTimeout()&lt;/code&gt; 或 &lt;code&gt;setInterval()&lt;/code&gt; 來計算時間，不僅每個人執行的結果不同，而且&lt;strong&gt;誤差還可能相當大&lt;/strong&gt;嗎？
在正式進入主題前，先來簡單介紹一下 &lt;code&gt;setTimeout()&lt;/code&gt; 與 &lt;code&gt;setInterval()&lt;/code&gt; 。&lt;/p&gt;
    
    </summary>
    
      <category term="名為 JavaScript 的黑魔法" scheme="https://kuro.tw/categories/%E5%90%8D%E7%82%BA-JavaScript-%E7%9A%84%E9%BB%91%E9%AD%94%E6%B3%95/"/>
    
    
      <category term="javascript" scheme="https://kuro.tw/tags/javascript/"/>
    
      <category term="重新認識 JavaScript" scheme="https://kuro.tw/tags/%E9%87%8D%E6%96%B0%E8%AA%8D%E8%AD%98-JavaScript/"/>
    
      <category term="setTimeout" scheme="https://kuro.tw/tags/setTimeout/"/>
    
  </entry>
  
  <entry>
    <title>企業前端框架趨勢論壇之問與答遺珠</title>
    <link href="https://kuro.tw/posts/2018/09/16/%E4%BC%81%E6%A5%AD%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6%E8%B6%A8%E5%8B%A2%E8%AB%96%E5%A3%87%E4%B9%8B%E5%95%8F%E8%88%87%E7%AD%94%E9%81%BA%E7%8F%A0/"/>
    <id>https://kuro.tw/posts/2018/09/16/企業前端框架趨勢論壇之問與答遺珠/</id>
    <published>2018-09-15T17:10:11.000Z</published>
    <updated>2019-12-06T11:24:51.959Z</updated>
    
    <content type="html"><![CDATA[<p>先感謝 Will保哥 的邀請，舉辦了一場<a href="https://www.accupass.com/event/1808150915324046311580" target="_blank" rel="noopener">企業前端框架趨勢論壇</a>的活動，來比較現代主流的三大前端框架 (Angular, Vue, React)，因為活動時間限制的關係，下半場讓會眾預先提問的問題，能夠回答的題目並不多 (<a href="https://app2.sli.do/event/g8wlredu/questions" target="_blank" rel="noopener">問券連結點此</a>)。 其中有些我認為還不錯或是有趣的題目，這裡就以 <strong>VueJS 推廣者</strong> 的角度來談談我對這些問題的看法。</p><p><img src="/static/img/ent-f2e-trend2.jpg"></p><a id="more"></a><hr><blockquote><p>Q: 常常看到 React、Angular、Vue 比較的文章，但是都是著重在技術本身，似乎很少比較應用面，我想知道什麼樣的技術比較適合開發什麼性質的網站，這樣比較容易了解適合導入哪種技術。</p></blockquote><p>答: 用什麼技術或是工具開發網站，我認為是其次，因為不管用什麼技術來開發，網站的本質都是一樣的，並不會因為某個網站用 VueJS 就寫得出來，用 React 或是 Angular 就寫不出來，當然反過來說也是。 程式是人寫出來的，技術選型首先考慮的是要不要使用框架，再來才是要使用什麼框架。</p><p>專案有它本身的複雜度，當然工具以及其生態圈也是。 如果 jQuery 甚至原生的 JavaScript 就可以解決你的需求，那麼也許你並不需要前端框架。 如果硬要在對 Vue、React、Angular 都是零經驗的基礎下挑選，我認為你可以隨便挑一套，先從小型、無長期維護需求的活動專案或是自己的 side project 開始試著實作看看，總會試出一套適合自己的，如果要問我的意見，一定是建議先挑 Vue.js 來試試看囉。</p><hr><blockquote><p>Q: 想請問前輩，如果遇到需要打掉重練（原：web layout刻板+jQ) 的問題，三種框架會怎麼引入或是怎麼解決呢（個人或顧問觀點）？ 如果帶新人，也是多以jQ為主的新人，要如何帶入框架的運用呢？（一口氣學完es6/TS，可能產能會降低一段時間 v.s. 如果有可能，一步步導入，……但如何操作呢？）</p></blockquote><p>答:這裡的問題我以 Vue.js 的角度來回答。 在舊專案 (假設原網站以 jQuery 開發) 的維護上，如果要轉換到 Vue.js 的話，首先可以採取以 <a href="https://vuejs.org/v2/guide/installation.html#CDN" target="_blank" rel="noopener">CDN 的方式</a> 來引進，用法完全與過去的 jQuery 一樣，把 Vue.js 引入 HTML 後 ，就可以直接在 <code>&lt;script&gt;</code> 區塊裡面寫 code 了。</p><p>當然未來專案規模成長，就可以再進一步將 component 拆分出獨立的 <code>.vue</code> 檔案，會更好維護。 至於 ES6 與 TypeScript 方面，因為 Vue CLI v3 內建 babel，所以 ES6 可以無痛轉換，而 TypeScript 以目前的狀況來說，要在原有的舊專案引進 TypeScript 的轉換成本較高 (比起 ES5 -&gt; ES6 ，ES -&gt; TS 要重寫的部分會比較多) ，我會建議未來開新專案時再直接引進。</p><hr><blockquote><p>Q: Vue背後沒有富爸爸。請問收掉的風險會不會比較大.</p></blockquote><p>答: 坦白說我滿意外這個問題有很多與會者關心。 雖說在 open source 的世界裡面沒有「收掉」這件事情，但確實有可能會「沒人繼續維護」，這點在 infoQ 最近對<a href="http://www.infoq.com/cn/news/2018/09/vue-v3-refactoring" target="_blank" rel="noopener">尤雨溪的專訪</a>中也有提及，像 Vue.js 這樣發展快速的年輕專案，相當依賴核心開發者是很正常的事，而 Vue.js 也漸漸朝向工程化的目標前進，就是<strong>期望未來可以減低這個專案對核心開發者的依賴</strong>，當有機會參與的人越多，這個開源的專案就有力量可以走得更長久。</p><p>就目前的狀況，不管是 Vue.js 框架本身的<a href="https://vuejs.org/v2/guide/team.html" target="_blank" rel="noopener">開發者團隊</a>也好，甚至是<a href="https://www.vuemeetups.org/find/" target="_blank" rel="noopener">世界各地的聚會</a>組織， Vue.js 的生態圈都還在持續成長的狀態。 另外在短期幾年來看，Vue.js 的作者尤雨溪對 Vue.js 的維護應該會持續下去，而他本人也很享受目前身為獨立開發者的狀態。</p><hr><blockquote><p>Q: 現職公司無使用三大框架但仍想接觸的人，該從什麼方向去練習專案開發</p></blockquote><p>答: 如同我前面所說，在三大框架都是零經驗的基礎下，開始可以隨便挑一套，先從小型、無長期維護需求的活動專案或是從自己的 side project 開始試著實作看看，覺得寫得下去就恭喜你，要是不合胃口就換一套。 總會試出一套適合自己的。</p><hr><blockquote><p>Q: 無師自通那是頂尖高手在做的事情，但是對於時間有限的一般開發者來說，目前有哪些書籍、官方文件以外的學習資源。</p></blockquote><p>答: 以 Vue.js 來說，在台灣 Facebook 有 <a href="https://www.facebook.com/groups/vuejs.tw/" target="_blank" rel="noopener">Vue.js Taiwan 台灣</a> 社團，線上討論的熱度一直都有，歡迎加入。 書籍的部分，天瓏書局也有引進相當多 Vue.js 為主題的書 (<a href="https://www.tenlong.com.tw/categories/vuejs" target="_blank" rel="noopener">連結</a>) 可供參考。</p><p>想要追最新資訊的話，這裡有 <a href="https://www.getrevue.co/profile/vuenewsletter" target="_blank" rel="noopener">Vue.js News</a> 可以訂閱。 如果沒有時間自己找資料的話，那麼挑選課程來學習也許是不錯的選擇。</p><p>如果是線上課程，這裡我推薦六角學院的 <a href="https://www.hexschool.com/vue/" target="_blank" rel="noopener">Vue.js 課程</a>。</p><p>當然如果你更偏好實體課程，覺得應該要面對面解決你的問題，那太好了，在台北的五倍紅寶石小弟我也有開設 <strong><a href="https://5xruby.tw/talks/vue-js/" target="_blank" rel="noopener">Vue.js 與 Vuex 前端開發實戰課程 - 假日班</a></strong>，<span style="color: #f00">最近一梯的開班日期就在九月下旬 (9/22) </span>喔。 (是業配廣告無誤，也是真心推薦)</p><hr><blockquote><p>Q: 之前看過一篇文章，內容提到將來可能會有&quot;以web component作為中介，讓目前流行的三大前端框架技術在同一專案中的整合運用&quot;這樣的趨勢，例如在angular 中使用其他兩個技術所建立的web component。想請問講師們關於web component的使用趨勢、這種整合方式的優缺點之類的。謝謝講師們！</p></blockquote><p>答: 這個問題應該先回到 Web Component 的本質。 以網頁標準來說， Web Component 並不只是單一的規範，從 W3C 擬定的<a href="https://www.w3.org/standards/techs/components#w3c_all" target="_blank" rel="noopener">標準</a> 來看，可以知道 Web Component 至少有這四個部分：</p><ul><li>Custom Elements</li><li>Shadow DOM</li><li>HTML Templates</li><li>HTML imports</li></ul><p>然而這四個規範的標準都是各自獨立的：像 Custom Elements 用來擴充 HTML 的語意，Shadow DOM 可以幫助我們封裝 DOM 與隔離元件之間的 CSS 樣式，HTML Templates 來達到 DOM 的動態生成，HTML imports 則是幫助我們引入外部的 HTML 內容。</p><p>這些標準看起來似乎很美好，但我們面臨的最大挑戰是什麼？並不是「最新」的瀏覽器對標準的支援不足，而是我們需要「向下相容」的瀏覽器對這些標準的實作程度，所以我們需要前端框架。 像 React、Angular、Vue 這樣的主流前端框架就以「自己的方式」來實作 Web Component。</p><p>讓我們回到問題。 就實作面來說，想以 Web Component 的標準作為中介整合各種前端工具，似乎還不可行。 但近期有個新的概念，叫「<a href="https://micro-frontends.org/" target="_blank" rel="noopener">Micro Frontends</a>, 微前端」，是從 Micro-services 的概念發展而來，並且應用至前端領域。</p><p>當我們的網站服務應用大到一定程度的時候，這樣的架構可以幫助我們將各個功能模組獨立開發、運行、測試與部署。</p><p>關於「微前端」的說明可以參考這兩篇文章：</p><ul><li><a href="https://blog.jimmylv.info/2017-12-24-tech-radar-microfrontends-extending-microservice-to-fed/" target="_blank" rel="noopener">技术雷达之「微前端」- 将微服务理念扩展到前端开发（上：理论篇）</a></li><li><a href="https://blog.jimmylv.info/2017-12-24-tech-radar-microfrontends-extending-microservice-to-fed-next/" target="_blank" rel="noopener">技术雷达之「微前端」- 将微服务理念扩展到前端开发（下：实战篇）</a></li></ul><hr><blockquote><p>Q: 怎麼樣才可以稱得上是資深前端工程師？是對瀏覽器的相容性很熟，還是對專案進展速度可以掌握？可以說說看你們認為的答案嗎</p></blockquote><p>答: 這個問題雖然與前端框架的關係不大，且每個人對「資深」的定義不盡相同。對我來說，至少要能做到獨力解決問題，並且能夠給團隊成員建議，以及對自我的要求，是資深工程師的基本要素。</p><p>另外想推薦大家這篇，好友 jaceju 寫的 <a href="http://jaceju.net/2016-12-24-be-a-senior-engineer/" target="_blank" rel="noopener">如何才有資格稱為資深工程師</a> 一文。</p><hr><blockquote><p>Q: Angular有雙向綁定，vue也以訂閱者/觀察者模式實踐雙向綁訂，需要理解背後的pattern再實做嗎？ **指新手盡量不要用到雙向綁定的做法？ ，還是框架設計出來用就對了，讓經驗慢慢理解…… 簡化問題，那種比較合適： “某天發現原來這是一個pattern” vs “先理解pattern才實做，否則盡量避免使用”</p></blockquote><p>答: 理解技術背後的實作細節絕對有助於專案的開發與除錯，但這並不是必備條件。 就好比我們學開車，沒必要非得理解引擎、輪胎是怎麼製造的之後才能學開車。</p><p>以 Vue.js 來說，如果是新手學習，我的建議是先參照官方的 <a href="https://cn.vuejs.org/v2/guide/index.html" target="_blank" rel="noopener">教學文件</a> 以及 <a href="https://cn.vuejs.org/v2/style-guide/index.html" target="_blank" rel="noopener">Style Guide</a> 的幫助最大，至少在學習初期不會走偏。 熟悉了基礎知識後，再去慢慢理解 <a href="https://cn.vuejs.org/v2/guide/custom-directive.html" target="_blank" rel="noopener">Custom Directive</a>、以及 <a href="https://cn.vuejs.org/v2/guide/reactivity.html" target="_blank" rel="noopener">變化追蹤的原理</a> 甚至是 webpack 等外圍生態圈的相關知識，會是比較建議的學習路線。</p><hr><blockquote><p>Q: Vue生態從早期是抽取angular某些功能（樣板語法/雙向綁定） ，到後期越來越往類似react的概念去構思核心 （redux-vuex,next.js-nuxt.js)，請問作為簡單上手的vue,如果往資深走的話，平時會選擇angular or react作為side project嗎？還是放手投入vue就好 😂</p></blockquote><p>答: 以技術選型的角度來看當然是多方嘗試更好，如果平時已經在用 Vue.js 作為主力開發，在 side project 可以改用 React、Angular 等其他前端框架來開發，這些工具之間雖然實作方式不同，但有不少概念是可以沿用的，在理解彼此的差異之後，對於未來做為技術選擇的參考會更有幫助。</p><hr><blockquote><p>Q: 想要了解vue的原始碼以及架構模式，從何而學？</p></blockquote><p>答: 如果對 Vue.js 的原始碼有興趣的話，可以推薦 <a href="https://ustbhuangyi.github.io/vue-analysis/" target="_blank" rel="noopener">Vue.js 技术揭秘</a> 這個電子書，對於 Vue.js 的原始碼有相當詳細的解說。</p><hr><blockquote><p>Q: 三套工具有幫助debug 的工具嗎</p></blockquote><p>答: Vue.js 有提供 <a href="https://github.com/vuejs/vue-devtools" target="_blank" rel="noopener">vue-devtools</a> 這個開發工具。</p><hr><blockquote><p>Q: 遇到產品使不同框架的過渡期，會建議全部統一一個框架，還是繼續做維護就好？實務上有沒有可能一個產品同時使用不同的框架</p></blockquote><p>答: 原有的專案與新專案 (或是專案改版) 的技術選擇確實有可能會不一樣，以開發角度來說，如果前後端的職責拆分夠乾淨的話，不管使用什麼框架都不會影響到新舊專案之間的維護與開發。</p><hr><blockquote><p>Q: 請問從 MPA 進展到 SPA （Angular/Vue/React）設計之下，GA(Google Analytics)/GTM(Google Tag Manager) 有哪些需要注意或改變的地方？</p></blockquote><p>答: MPA 與 SPA 在 Google Analytics 的用法基本上差異不大，比較需要特別注意的是，由於 SPA 的換頁其實是假的 (透過 HTML5 History API 操作)，所以在換頁的時候，需要注意 virtual pageviews 的追蹤。</p><p>參考連結: <a href="https://developers.google.com/analytics/devguides/collection/analyticsjs/single-page-applications" target="_blank" rel="noopener">Single Page Application Tracking</a></p><hr><blockquote><p>Q: 使用Vue的寫法與往常最大的差異在哪裡？</p></blockquote><p>答: Vue.js 與過去用 jQuery 這類工具最大的不同之處是在於對 DOM 操作思維的不同。過去用 jQuery 開發的時候，幾乎就是一行指令做一件事，雖然直覺，但是當專案規模慢慢變大，尤其像 SPA 的應用等，維護上是不容易的。</p><p>而現代多數 MVVM 框架的開發模式，會把關注點放在資料/狀態的管理，畫面與資料的同步由工具來幫我們處理掉，開發者需要關注的是資料與狀態的部分。</p><p>這裡可以參考我之前的投影片： <a href="https://speakerdeck.com/kurotanshi/jsdc2017-cong-vuejs-kan-qian-duan-sheng-tai-quan-de-fa-zhan-bian-hua" target="_blank" rel="noopener">[JSDC2017] 從 VueJS 看前端生態圈的發展變化</a>，以及 <a href="https://yami.io/jquery-to-vuejs/" target="_blank" rel="noopener">脫離資料分散的問題，從 jQuery 換到 Vue.js</a> 這篇文章。</p><h2 id="最後再次工商服務"><a href="#最後再次工商服務" class="headerlink" title="最後再次工商服務"></a>最後再次工商服務</h2><p>這是我最近在五倍紅寶石開設的 VueJS 入門課程，時間在九月底。如果你對 VueJS 有興趣，卻總是不得其門而入，歡迎點此 <a href="https://5xruby.tw/talks/vue-js" target="_blank" rel="noopener">https://5xruby.tw/talks/vue-js</a> 報名參加，聽說目前有打折，而且折扣還不少喔。</p><iframe src="https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2F5xruby%2Fposts%2F890142061185403&width=500" width="500" height="440" style="border:none;overflow:hidden;max-width:100%;" scrolling="no" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><hr><h2 id="結論"><a href="#結論" class="headerlink" title="結論"></a>結論</h2><p>雖然前端框架這些年不斷推陳出新，但在面對這些百花繚亂的工具時，我認爲認清背後的本質是更重要的。<a href="https://blog.chunfuchao.com/?p=656&amp;variant=zh-tw" target="_blank" rel="noopener">一窩蜂</a>盲目地追求新技術，其實是沒有意義的。技術是為了解決問題而生，當我們在面對技術的時候，事實上我們應該要先面對問題本身。</p><p>工具不會橫空出世，都是先有問題才有解決的方案。</p><p>這個方案可能不夠完美，那個問題也許有很多種解法，那麼哪一種才是適合自己的呢？ 他們之間的優缺點以及各自的取捨又是什麼？ 回歸問題目的層面，先釐清目標是什麼，再回頭來看解決的手段。</p><p><img src="/static/img/ent-f2e-trend.jpg"></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;先感謝 Will保哥 的邀請，舉辦了一場&lt;a href=&quot;https://www.accupass.com/event/1808150915324046311580&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;企業前端框架趨勢論壇&lt;/a&gt;的活動，來比較現代主流的三大前端框架 (Angular, Vue, React)，因為活動時間限制的關係，下半場讓會眾預先提問的問題，能夠回答的題目並不多 (&lt;a href=&quot;https://app2.sli.do/event/g8wlredu/questions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;問券連結點此&lt;/a&gt;)。 其中有些我認為還不錯或是有趣的題目，這裡就以 &lt;strong&gt;VueJS 推廣者&lt;/strong&gt; 的角度來談談我對這些問題的看法。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/static/img/ent-f2e-trend2.jpg&quot;&gt;&lt;/p&gt;
    
    </summary>
    
      <category term="vue.js" scheme="https://kuro.tw/categories/vue-js/"/>
    
    
      <category term="vue.js" scheme="https://kuro.tw/tags/vue-js/"/>
    
      <category term="react" scheme="https://kuro.tw/tags/react/"/>
    
      <category term="angular" scheme="https://kuro.tw/tags/angular/"/>
    
      <category term="企業前端框架趨勢論壇" scheme="https://kuro.tw/tags/%E4%BC%81%E6%A5%AD%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6%E8%B6%A8%E5%8B%A2%E8%AB%96%E5%A3%87/"/>
    
  </entry>
  
  <entry>
    <title>Vue.js 與 CSS Modules</title>
    <link href="https://kuro.tw/posts/2018/09/07/Vue-js-%E8%88%87-CSS-Modules/"/>
    <id>https://kuro.tw/posts/2018/09/07/Vue-js-與-CSS-Modules/</id>
    <published>2018-09-07T06:19:36.000Z</published>
    <updated>2019-12-06T11:28:23.415Z</updated>
    
    <content type="html"><![CDATA[<p>去年我寫了一篇「<a href="/posts/2017/07/26/%E5%BE%9EVue%E4%BE%86%E7%9C%8BCSS%E7%AE%A1%E7%90%86%E6%96%B9%E6%A1%88%E7%9A%84%E7%99%BC%E5%B1%95/">從 Vue 來看 CSS 管理方案的發展</a>」來談現代主流前端框架對 CSS 的各種處理方案，相信對 Vue.js 已經熟悉的朋友，都知道 Vue file 裡面是透過「Scoped CSS」也就是 <code>&lt;style&gt;</code> 內的 <code>scoped</code> 屬性來隔離 component 之間的樣式規則。</p><a id="more"></a><p>即便如此，<a href="https://vue-loader.vuejs.org/guide/" target="_blank" rel="noopener">Vue-Loader</a> 在 v9.8.0 之後也內建了 CSS Modules 的整合，提供開發者另一種不同的 scoped CSS 替代方案。</p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>CSS Modules 的概念，我想最早大約可以從 2013、14 年左右說起。</p><p>當時前端領域可以說是進入了前所未見的爆炸發展，JavaScript 各種框架如雨後春筍般冒出，而 CSS 的管理卻沒有一個好的方式，很大的原因在於 CSS 的程式化與 JavaScript 比較其實是相對困難的，至少在命名管理以及 CSS 的有效範圍 Scope 一直是 web 開發者長年的夢靨。 工具方面，雖然有 LESS、SASS、Stylus 這類的預處理器 (preprocessor) 來幫助我們做到繼承、重用、複寫等功能，然而即便是這樣，global scoped CSS 的問題卻始終沒有一個好的解決辦法。</p><p>當然，也有另一派人馬提倡的是，由 CSS 的命名學 / 架構論 來達到 CSS 模組的管理與複用，如 OOCSS、SMACSS、BEM 等。</p><p>不過遺憾的是，人性天生就是懶，像 BEM <del>這麼囉唆</del>的命名方式天生就違反人性。 規範一旦沒有嚴格遵守，那就跟沒有一樣，更不用說手動處理還可能出錯。</p><p>於是後來在 React 陣營就出現了 <a href="https://github.com/css-modules/css-modules" target="_blank" rel="noopener">CSS Modules</a> 這類的解決方案 [註]，透過工具來處理那些人工手動處理命名規範要做的事。 也就是說，由 webpack 做了 BEM 的事情，過去 BEM 是人工手動做，而 webpack 是交給工具自動化做，以類似 Javascript module 的方式來處理 CSS，簡化了人工流程，也減少了手動犯錯的機會。</p><h2 id="如何使用"><a href="#如何使用" class="headerlink" title="如何使用"></a>如何使用</h2><p>幸運的是，Vue-loader 在 v9.8.0 之後，CSS Modules 已經內建整合到我們的開發環境了。 所以當我們透過 vue-cli v3 執行 <code>vue create [專案名稱]</code> 建立專案後，完全無須多餘的設定，就可以直接來使用 CSS Modules。</p><p>使用的方式很簡單，我們只要在 <code>&lt;style&gt;</code> 加上 <code>module</code> 屬性，如：</p><pre><code class="html">&lt;style module&gt;.red {  color: red;}&lt;/style&gt;</code></pre><p>然後，我們在 <code>template</code> 裡面這樣寫：</p><pre><code class="html">&lt;template&gt;  &lt;div id=&quot;app&quot;&gt;    &lt;h1 :class=&quot;$style.red&quot;&gt;Hello Vue!&lt;/h1&gt;  &lt;/div&gt;&lt;/template&gt;</code></pre><p><img src="/static/img/css-modules/css-modules-1.png"></p><p>這時候，我們打開 console 來觀察，就會發現模板上的 <code>:class=&quot;$style.red&quot;</code> 會被 <a href="https://github.com/vuejs/vue/tree/dev/packages/vue-template-compiler#readme" target="_blank" rel="noopener">vue-template-compiler</a> 編譯成為 <code>App_red_DWJpy</code> 這個 class，並且在 <code>style</code> 也自動生成對應的樣式規則。</p><p>但是這裡要特別注意的是，如果你在 css modules 裡面的取名有分隔線，如:</p><pre><code class="html">&lt;style module&gt;.title-color {  color: red;}&lt;/style&gt;</code></pre><p>在透過 <code>$style</code> 取用的時候，如果寫成 <code>$style.title-color</code> 這是一個不合法的 JS 變數名稱，而且也不能寫成 <code>$style.titleColor</code>，只能透過 <code>$style[&quot;title-color&quot;]</code> 的方式來取得。</p><p>上面說的 <code>module</code> 屬性會經由 Vue-Loader 編譯後，在我們的 component 產生一個叫 <code>$style</code> 的隱藏 computed 屬性。換句話說，我們甚至可以在 <code>script</code> 裡面取得由 CSS Modules 生成的 class 名稱：</p><pre><code class="html">&lt;script&gt;export default {  created () {    console.log(this.$style.red);  // App_red_DWJpy  }}&lt;/script&gt;</code></pre><p>反過來說，我們當然也可以利用這種特性，在 <code>template</code> 這樣寫：</p><pre><code class="html">&lt;template&gt;  &lt;div id=&quot;app&quot;&gt;    &lt;p :class=&quot;{ [$style.blue]: isBlue }&quot;&gt;Am I blue?&lt;/p&gt;    &lt;p :class=&quot;[$style.red, $style.bold]&quot;&gt;Red and bold&lt;/p&gt;  &lt;/div&gt;&lt;/template&gt;</code></pre><pre><code class="html">&lt;script&gt;export default {  name: &#39;app&#39;,  data (){    return {      isBlue: true    }  }}&lt;/script&gt;</code></pre><pre><code class="html">&lt;style module&gt;.red {  color: red;}.blue {  color: blue;}.bold {  font-weight: bold;}&lt;/style&gt;</code></pre><p><img src="/static/img/css-modules/css-modules-2.png"></p><p>如上圖，當 <code>data</code> 裡的 <code>isBlue</code> 屬性為 <code>true</code> 時，「Am I blue?」的字樣就會新增對應的 class 變成藍色。</p><p>甚至透過 <code>props</code> 將 class 傳入子層元件也是可以的，像這樣：</p><pre><code class="html">&lt;template&gt;  &lt;div id=&quot;app&quot;&gt;    &lt;img alt=&quot;Vue logo&quot; src=&quot;./assets/logo.png&quot;&gt;    &lt;HelloWorld      msg=&quot;Welcome to Your Vue.js App&quot;      :titleClass=&quot;$style.titleColor&quot;    /&gt;  &lt;/div&gt;&lt;/template&gt;</code></pre><pre><code class="html">&lt;style module&gt;.titleColor{  color: #f00;}&lt;/style&gt;</code></pre><p>子層元件 <code>HelloWorld.vue</code> ：</p><pre><code class="html">&lt;script&gt;export default {  name: &#39;HelloWorld&#39;,  props: {    msg: String,    titleClass: String  }}&lt;/script&gt;</code></pre><pre><code class="html">&lt;template&gt;  &lt;div class=&quot;hello&quot;&gt;    &lt;h1 :class=&quot;titleClass&quot;&gt;{{ msg }}&lt;/h1&gt;  &lt;/div&gt;&lt;/template&gt;</code></pre><p>像這樣，我們就可以在 <code>template</code> 用 vue 的 class binding 語法來處理樣式了。這也意味著我們在控制樣式的時候，可以更輕鬆地利用程式來組織。</p><hr><p>如果我們想要在 JavaScript 裡面將獨立的 css 檔案作為 CSS Modules 來載入的話，要記得在檔名加上 <code>.module.</code> 的前綴，如：</p><pre><code class="js">import styles from &#39;./foo.module.css&#39;</code></pre><p>當然透過像 less/sass/scss/stylus 等預處理器編譯的文件也是 (相關 loader 要另行安裝) ：</p><pre><code class="js">// works for all supported pre-processors as wellimport sassStyles from &#39;./foo.module.scss&#39;</code></pre><p>如果不想在檔名加入 <code>.module.</code> 的前綴，則可以在 <code>vue.config.js</code> 裡面的 <code>css.modules</code> 設定為 <code>true</code>：</p><pre><code class="js">// vue.config.jsmodule.exports = {  css: {    modules: true  }}</code></pre><p>以上就是在 Vue 的開發流程中使用 CSS Modules 的簡單介紹，關於 CSS Modules 的更多特性，大家可以參考這份 <a href="https://github.com/css-modules/css-modules" target="_blank" rel="noopener">官方文件</a> 會有更多詳細的說明。</p><h2 id="工商服務插播"><a href="#工商服務插播" class="headerlink" title="工商服務插播"></a>工商服務插播</h2><p>這是我最近在五倍紅寶石開設的 VueJS 入門課程，時間在九月底。如果你對 VueJS 有興趣，卻總是不得其門而入，歡迎點此 <a href="https://5xruby.tw/talks/vue-js" target="_blank" rel="noopener">https://5xruby.tw/talks/vue-js</a> 報名參加，聽說早鳥票有打折，而且折扣還不少。</p><iframe src="https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2F5xruby%2Fposts%2F890142061185403&width=500" width="500" height="440" style="border:none;overflow:hidden;max-width:100%;" scrolling="no" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><h2 id="結論"><a href="#結論" class="headerlink" title="結論"></a>結論</h2><p>不管是 Scoped Style 或是 CSS Modules ，其實都可以發現它們在使用上相當簡單，也在某種程度上解決了同樣的問題。 就開發上來說，Scoped Style 可以帶來完全無痛的開發體驗，在 <code>&lt;style&gt;</code> 加個 <code>scoped</code> 就可以隔離 component 之間的樣式。</p><p>但是當 css style 需要在多個 component 被 reuse 的時候，Scoped Style 在這方面反而顯得有些力不從心。 這時 CSS Modules 的出現，正好解決了這個問題，代價就是需要透過 <code>$style</code> 這個被生成的屬性來控制。 當然，適合的場景不同，彼此之間各有優缺點，端看當時專案的規模與需求來挑選使用就可以了。</p><hr><p>註： 有人整理了一份 CSS-in-JS 的各種方案比較： <a href="https://github.com/MicheleBertoli/css-in-js" target="_blank" rel="noopener">https://github.com/MicheleBertoli/css-in-js</a> ，<a href="https://github.com/css-modules/css-modules" target="_blank" rel="noopener">CSS Modules</a> 也是其中之一。 如果你對 CSS-in-JS 有興趣的話，這裏推薦閱讀這篇 <a href="https://hackernoon.com/all-you-need-to-know-about-css-in-js-984a72d48ebc" target="_blank" rel="noopener">All You Need To Know About CSS-in-JS</a> 非常徹底地解釋了各種關於 CSS-in-JS 的原理與想法。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;去年我寫了一篇「&lt;a href=&quot;/posts/2017/07/26/%E5%BE%9EVue%E4%BE%86%E7%9C%8BCSS%E7%AE%A1%E7%90%86%E6%96%B9%E6%A1%88%E7%9A%84%E7%99%BC%E5%B1%95/&quot;&gt;從 Vue 來看 CSS 管理方案的發展&lt;/a&gt;」來談現代主流前端框架對 CSS 的各種處理方案，相信對 Vue.js 已經熟悉的朋友，都知道 Vue file 裡面是透過「Scoped CSS」也就是 &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; 內的 &lt;code&gt;scoped&lt;/code&gt; 屬性來隔離 component 之間的樣式規則。&lt;/p&gt;
    
    </summary>
    
      <category term="vue.js" scheme="https://kuro.tw/categories/vue-js/"/>
    
    
      <category term="vue.js" scheme="https://kuro.tw/tags/vue-js/"/>
    
      <category term="CSS Modules" scheme="https://kuro.tw/tags/CSS-Modules/"/>
    
  </entry>
  
  <entry>
    <title>利用 Google 試算表 (Google Sheet) 作為外部資料來源</title>
    <link href="https://kuro.tw/posts/2018/08/27/%E5%88%A9%E7%94%A8-Google-%E8%A9%A6%E7%AE%97%E8%A1%A8-Google-Sheet-%E4%BD%9C%E7%82%BA%E5%A4%96%E9%83%A8%E8%B3%87%E6%96%99%E4%BE%86%E6%BA%90/"/>
    <id>https://kuro.tw/posts/2018/08/27/利用-Google-試算表-Google-Sheet-作為外部資料來源/</id>
    <published>2018-08-27T07:33:08.000Z</published>
    <updated>2019-12-06T11:25:21.403Z</updated>
    
    <content type="html"><![CDATA[<p><img src="/static/img/google-sheets-api/google-sheets.jpg" alt></p><p>最近收到了奇怪的需求，要把其他單位整理好的 Excel 檔案轉成 JSON 格式，並在網頁上呈現。</p><p>由於「懶」是身為碼農的最大美德，當然能用程式解決的問題就不要手動複製貼上，於是想到了可以利用  <a href="https://docs.google.com/spreadsheets/" target="_blank" rel="noopener">Google Sheet</a> API 來幫我們解決這類問題。</p><a id="more"></a><hr><p><img src="/static/img/google-sheets-api/google-sheets-1.png" alt></p><p>這裡我們以 Data.Taipei 的 <a href="http://data.taipei/opendata/datalist/datasetMeta?oid=8fb40583-cb9a-4df3-983a-9f9a6c9d9bd5" target="_blank" rel="noopener">106年多元繳稅統計表</a> 為例，首先把 106年多元繳稅統計表 的 excel 檔案下載下來。</p><p>接著到 <a href="https://docs.google.com/spreadsheets/" target="_blank" rel="noopener">Google Sheet</a> 建立一份全新的試算表，然後點選 「檔案」 &gt; 「匯入」。</p><p><img src="/static/img/google-sheets-api/google-sheets-2.png" alt></p><p>將剛剛的檔案上傳上去，這裡可能需要花點時間，請耐心等候。</p><p><img src="/static/img/google-sheets-api/google-sheets-3.png" alt></p><p><img src="/static/img/google-sheets-api/google-sheets-4.png" alt></p><p>完成後應該會看到這樣的畫面。</p><p><img src="/static/img/google-sheets-api/google-sheets-5.png" alt></p><p>資料的準備到目前為止算是完成了。</p><hr><p>那麼我們要怎麼透過 Google Sheet API 來取得這份資料呢？</p><p>第一步，我們點選工具列的「檔案」 &gt; 「發佈到網路」，然後點一下藍色的「發佈」。</p><p><img src="/static/img/google-sheets-api/google-sheets-6.png" alt></p><p><img src="/static/img/google-sheets-api/google-sheets-7.png" alt></p><p>這個時候會跳出對話框，選確定就好。</p><p><img src="/static/img/google-sheets-api/google-sheets-8.png" alt></p><p>然後會出現一串網址，別管它，直接點右上的 x 關閉。</p><p><img src="/static/img/google-sheets-api/google-sheets-9.png" alt></p><p>接下來才是重頭戲。</p><hr><p>取用 Google Sheet 的網址規則是這樣的：</p><pre style="margin: 2em 0; padding: 10px; background: #eee; overflow-x: scroll !important;">https://spreadsheets.google.com/feeds/cells/[<b style="color: #f00;">KEY</b>]/[<b style="color: #f00;">SHEET INDEX</b>]/public/values?alt=json</pre><p>那這份 key 要從哪裡來呢？ 其實這份 key 就在網址上面 (如圖選取處)：</p><p><img src="/static/img/google-sheets-api/google-sheets-10.png" alt></p><p>換句話說，我們要是想要取用第一個 sheet (10601) 的話，可以直接透過這個 url:<a href="https://spreadsheets.google.com/feeds/cells/1RqFglfTPqhcxwpdY6007bxKkaU7FeW_GTl9F2jKCHTY/1/public/values?alt=json" target="_blank" rel="noopener">https://spreadsheets.google.com/feeds/cells/1RqFglfTPqhcxwpdY6007bxKkaU7FeW_GTl9F2jKCHTY/1/public/values?alt=json</a></p><p>第二個就把 <strong>/1/</strong> 換成 <strong>/2/</strong> 就好了。</p><p>取回來的資料都會在 <code>feed.entry</code> 裡面，我們可以透過 <code>gs$cell</code> 來找到所屬的欄/列，以及儲存格的內容。</p><p><img src="/static/img/google-sheets-api/google-sheets-11.png" alt></p><p>透過 Google Sheet API 拿回來的資料預設會帶有 CORS Header，所以不受跨 domain 存取限制，很方便吧！</p><p>感恩 Google! 讚嘆 Google!</p><h2 id="工商服務時間"><a href="#工商服務時間" class="headerlink" title="工商服務時間"></a>工商服務時間</h2><p>這是我最近在五倍紅寶石開設的 VueJS 入門課程，時間在九月底。如果你對 VueJS 有興趣，卻總是不得其門而入，歡迎點此 <a href="https://5xruby.tw/talks/vue-js" target="_blank" rel="noopener">https://5xruby.tw/talks/vue-js</a> 報名參加，聽說早鳥票有打折，而且折扣還不少。</p><iframe src="https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2F5xruby%2Fposts%2F890142061185403&width=500" width="500" height="440" style="border:none;overflow:hidden;max-width:100%;" scrolling="no" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;img src=&quot;/static/img/google-sheets-api/google-sheets.jpg&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;最近收到了奇怪的需求，要把其他單位整理好的 Excel 檔案轉成 JSON 格式，並在網頁上呈現。&lt;/p&gt;
&lt;p&gt;由於「懶」是身為碼農的最大美德，當然能用程式解決的問題就不要手動複製貼上，於是想到了可以利用  &lt;a href=&quot;https://docs.google.com/spreadsheets/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Google Sheet&lt;/a&gt; API 來幫我們解決這類問題。&lt;/p&gt;
    
    </summary>
    
      <category term="名為 JavaScript 的黑魔法" scheme="https://kuro.tw/categories/%E5%90%8D%E7%82%BA-JavaScript-%E7%9A%84%E9%BB%91%E9%AD%94%E6%B3%95/"/>
    
    
      <category term="Google Sheet API" scheme="https://kuro.tw/tags/Google-Sheet-API/"/>
    
  </entry>
  
  <entry>
    <title>VueJS 元件 (Component) 之間資料溝通傳遞的方式</title>
    <link href="https://kuro.tw/posts/2018/08/22/VueJS-%E5%85%83%E4%BB%B6-Component-%E4%B9%8B%E9%96%93%E8%B3%87%E6%96%99%E5%82%B3%E9%81%9E%E7%9A%84%E6%96%B9%E5%BC%8F/"/>
    <id>https://kuro.tw/posts/2018/08/22/VueJS-元件-Component-之間資料傳遞的方式/</id>
    <published>2018-08-22T04:30:00.000Z</published>
    <updated>2019-12-06T11:28:04.982Z</updated>
    
    <content type="html"><![CDATA[<p><img src="/static/img/vue-instance.png"></p><p>由於 VueJS 採用元件系統 (Component System) 來組織我們的應用程式，元件之間的資料傳遞，一直都是個不容忽視的問題，尤其在過去我們看過太多資料傳遞不當處理的方式，專案隨著時間不斷擴張，變得難以維護，最終導致不得不砍掉重練的悲劇。</p><a id="more"></a><h2 id="讓我們先從一個最簡單的範例說起吧！"><a href="#讓我們先從一個最簡單的範例說起吧！" class="headerlink" title="讓我們先從一個最簡單的範例說起吧！"></a>讓我們先從一個最簡單的範例說起吧！</h2><pre><code class="html">  &lt;div id=&quot;app&quot;&gt;    &lt;button @click=&quot;count++&quot;&gt;You clicked me {{ count }} times.&lt;/button&gt;  &lt;/div&gt;</code></pre><pre><code class="javascript">  var app = new Vue({    el: &#39;#app&#39;,    data: {      count: 0    }  });</code></pre><p>上面是一個點擊計數器的範例，當我們點擊畫面按鈕的時候，按鈕裡面的數字也會隨著增加。</p><p><img src="/static/img/vue-component-data-transfer/button.png"><a href="http://output.jsbin.com/bedeho" target="_blank" rel="noopener">Demo link</a></p><p>接著，如果我們需要增加另一個計數器呢？</p><p>聰明的你也許會想到，那我們就複製一份 <code>button</code> 吧！</p><pre><code class="html">  &lt;div id=&quot;app&quot;&gt;    &lt;button @click=&quot;count++&quot;&gt;You clicked me {{ count }} times.&lt;/button&gt;    &lt;button @click=&quot;count++&quot;&gt;You clicked me {{ count }} times.&lt;/button&gt;  &lt;/div&gt;</code></pre><p>此時畫面雖然變成兩個按鈕，但問題來了，當我按下其中一個按鈕的時候，兩個按鈕內的 <code>count</code> 都增加了！ 這是因為這兩個按鈕<strong>共用</strong>同一份 <code>count</code> 的狀態。</p><p><img src="/static/img/vue-component-data-transfer/button-sync.gif"><a href="https://jsbin.com/neberoc/1/edit?html,js,output" target="_blank" rel="noopener">Demo link</a></p><p>此時，你可能會說，那我就改成 <code>count1</code> 、 <code>count2</code> ... 這樣新增下去。</p><p>沒錯，這樣雖然可以避開錯誤，讓每一個按鈕擁有各自的狀態，但我們的程式也失去了彈性，有幾個按鈕就必須事先新增幾個 count 的屬性。</p><p>這個時候，就需要利用 VueJS 的元件系統來將計數器切分成各自的元件。</p><p><img src="/static/img/vue-instance.png"></p><p>在 VueJS 每個元件實體都有自己的資料作用範圍 (通常稱之為 scope)，每個元件的資料都是<strong>獨立</strong>的。 換言之，我們無法直接從某個元件去存取其他元件的資料。</p><p>這樣講可能不太好理解，讓我們改寫一下剛剛的計數器範例：</p><pre><code class="html">  &lt;div id=&quot;app&quot;&gt;    &lt;button-counter&gt;&lt;/button-counter&gt;    &lt;button-counter&gt;&lt;/button-counter&gt;  &lt;/div&gt;</code></pre><pre><code class="javascript">Vue.component(&#39;button-counter&#39;, {  data: function () {    return {      count: 0    }  },  template: &#39;&lt;button @click=&quot;count++&quot;&gt;You clicked me {{ count }} times.&lt;/button&gt;&#39;})var app = new Vue({  el: &#39;#app&#39;});</code></pre><p>像這樣，我們將計數器包裝成獨立的元件 <code>&lt;button-counter&gt;</code>，雖然同樣是計數器元件，但各自的 <code>count</code> 資料屬性都是獨立的，並不會因為按了 A 按鈕，B 按鈕就跟著加一。</p><p>如果要新增多組計數器，我們也只需要在 View 中繼續增加 <code>&lt;button-counter&gt;</code> 的數量就可以了，不必擔心彼此的資料是否衝突。</p><p><img src="/static/img/vue-component-data-transfer/button-no-sync.gif"><a href="https://jsbin.com/bigomeh/edit?html,js,output" target="_blank" rel="noopener">Demo link</a></p><p>完美！</p><h2 id="工商服務插播"><a href="#工商服務插播" class="headerlink" title="工商服務插播"></a>工商服務插播</h2><p>這是我最近在五倍紅寶石開設的 VueJS 入門課程，時間在九月底。如果你對 VueJS 有興趣，卻總是不得其門而入，歡迎點此 <a href="https://5xruby.tw/talks/vue-js" target="_blank" rel="noopener">https://5xruby.tw/talks/vue-js</a> 報名參加，聽說早鳥票有打折，而且折扣還不少。</p><iframe src="https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2F5xruby%2Fposts%2F890142061185403&width=500" width="500" height="440" style="border:none;overflow:hidden;max-width:100%;" scrolling="no" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><hr><h2 id="父與子：-「props-in-events-out」"><a href="#父與子：-「props-in-events-out」" class="headerlink" title="父與子： 「props in, events out」"></a>父與子： 「props in, events out」</h2><p>當然事情不會如你我所想得這麼簡單，此時問題又來了！</p><p>假設今天有個需求，想要知道所有按鈕被按下的次數，也就是說，就是要計算所有 <code>&lt;button-counter&gt;</code> 的 <code>count</code>，並且加總。 在元件資料各自為政的情況下，我們該如何處理？</p><p>當然我們不能直接在某個元件去<strong>直接存取</strong>另一個元件的資料，在 VueJS 裡面處理元件資料的時候，有個很重要的觀念：</p><h3 id="「props-in-events-out」"><a href="#「props-in-events-out」" class="headerlink" title="「props in, events out」"></a>「props in, events out」</h3><p><img src="/static/img/props-in-events-out.png"></p><p>資料透過 props 傳入，而更新透過 events 觸發。</p><p>關於 props 的部份我們稍後再提。</p><hr><p>現在我們要做 <code>count</code> 的加總，那麼首先，我們就在父層 <code>#app</code> 上加上一個資料屬性： <code>sum</code> 來儲存各個元件的總量，並且在 HTML 把它顯示出來：</p><pre><code class="html">  &lt;div id=&quot;app&quot;&gt;    &lt;h1&gt;Total: {{sum}}&lt;/h1&gt;    &lt;button-counter&gt;&lt;/button-counter&gt;    &lt;button-counter&gt;&lt;/button-counter&gt;  &lt;/div&gt;</code></pre><pre><code class="javascript">Vue.component(&#39;button-counter&#39;, {  data: function () {    return {      count: 0    }  },  template: &#39;&lt;button @click=&quot;count++&quot;&gt;You clicked me {{ count }} times.&lt;/button&gt;&#39;})var app = new Vue({  el: &#39;#app&#39;,  data: {    sum: 0  }});</code></pre><p><img src="/static/img/vue-component-data-transfer/no-events.gif"><a href="https://jsbin.com/qoduvovare/1/edit?html,js,output" target="_blank" rel="noopener">Demo link</a></p><p>像這樣，雖然畫面顯示了「Total: 0」的字樣，但是當我們按下按鈕的時候，並未有任何反應。</p><p>你沒看錯，因為我們只是新增了 sum，程式還沒寫。</p><hr><p>還記得剛剛曾說過的「<strong>props in, events out</strong>」嗎? 雖然我們不能在 <code>&lt;button-counter&gt;</code> 裡面控制父層的 <code>sum</code>，但我們可以透過「<strong>事件</strong>」來讓父層去觸發資料更新。</p><p>要觸發事件，自然要先註冊事件。</p><p>第一步，在 view 上面加上 <code>@add-sum=&quot;sum++&quot;</code> 來註冊我們的自訂事件「add-sum」，在 <code>add-sum</code> 這個事件被觸發的時候，會對父層的做 <code>sum++</code>。</p><pre><code class="html">  &lt;div id=&quot;app&quot;&gt;    &lt;h1&gt;Total: {{sum}}&lt;/h1&gt;    &lt;button-counter @add-sum=&quot;sum++&quot;&gt;&lt;/button-counter&gt;    &lt;button-counter @add-sum=&quot;sum++&quot;&gt;&lt;/button-counter&gt;  &lt;/div&gt;</code></pre><p>那麼，在 <code>button-counter</code> 的部分，則是在原本的 click 事件中，加入 <code>$emit(&#39;add-sum&#39;)</code> 來表示當使用者點擊按鈕的同時，也要跟著發送 <code>add-sum</code> 事件：</p><pre><code class="javascript">Vue.component(&#39;button-counter&#39;, {  data: function () {    return {      count: 0    }  },  template: &#39;&lt;button @click=&quot;count++; $emit(\&#39;add-sum\&#39;)&quot;&gt;You clicked me {{ count }} times.&lt;/button&gt;&#39;,});</code></pre><p><img src="/static/img/vue-component-data-transfer/event-out.gif"><a href="https://jsbin.com/numipawowe/1/edit?html,js,output" target="_blank" rel="noopener">Demo link</a></p><p>於是現在我們在點擊按鈕的時候，也能同時透過觸發「事件」去通知上層元件去更新對應資料，而不是直接存取父層的 data 屬性，這就是前面說的「Events out」。</p><hr><p>那麼 Props in 呢？</p><p>前面說過每個元件都有各自獨立的 Scope，但有時候我們元件內的資料是需要從外部引進來的，這時我們就必須要透過 「Props」這個屬性來宣告我們要從外部引進的資料。</p><p>一樣是前面的計數器範例。</p><p>假設 PM 今天又新增了一個需求：「欸欸，我的計數器<strong>可不可以不要從零開始跳，我想指定從某個數字開始</strong>...」</p><p>『<strong>當然不可以啊</strong>』</p><p>............ 雖然我知道你很想這樣回，但現實總是殘酷，實話永遠傷人。</p><p>回到程式。 最簡單的用法就是在 component 裡面新增 props 這個屬性：</p><pre><code class="javascript">Vue.component(&#39;button-counter&#39;, {  props: {    [&#39;initialCounter&#39;]  },  data: function () {    return {      count: this.initialCounter    }  },  template: &#39;&lt;button @click=&quot;count++; $emit(\&#39;add-sum\&#39;)&quot;&gt;You clicked me {{ count }} times.&lt;/button&gt;&#39;,});</code></pre><p>像這樣，我們就新增了一個叫 <code>initialCounter</code> 的 props，而原本的 <code>count</code> 也改為由 props 帶入的 <code>this.initialCounter</code> 來表示。</p><p>props 宣告完成了，那麼，該怎麼把資料帶到 <code>button-counter</code> 呢？</p><p>很簡單，這裡我們來改寫一下 HTML 的部分：</p><pre><code class="html">  &lt;div id=&quot;app&quot;&gt;    &lt;h1&gt;Total: {{sum}}&lt;/h1&gt;    &lt;button-counter :initial-counter=&quot;10&quot; @add-sum=&quot;sum++&quot;&gt;&lt;/button-counter&gt;    &lt;button-counter :initial-counter=&quot;20&quot; @add-sum=&quot;sum++&quot;&gt;&lt;/button-counter&gt;  &lt;/div&gt;</code></pre><p>這裏需要注意的是，由於 HTML 不分大小寫的特性，我們定義的 <code>initialCounter</code> props，必須要轉成 <strong>kebab-case</strong> (單字與單字用 - 符號連結) 的寫法，才能正確傳入喔。</p><p>像這樣，現在我們就可以針對不同計數器去個別定義它們的初始數值了！</p><p><img src="/static/img/vue-component-data-transfer/init-num.gif"><a href="https://jsbin.com/hinixokiwa/1/edit?html,js,output" target="_blank" rel="noopener">Demo link</a></p><p>就這樣，一天又平安的過去了，感謝飛天小女警的努力！</p><hr><h2 id="老司機送貨囉：-Event-bus"><a href="#老司機送貨囉：-Event-bus" class="headerlink" title="老司機送貨囉： Event bus"></a>老司機送貨囉： Event bus</h2><p>前面提到元件間的溝通，如果是單純的父子層級，透過 props 與 events 來處理顯然沒有什麼問題。但是我們的專案不會總是只有父子兩層這樣單純的關係，若是同層間的元件或是跨層級元件的互相溝通呢？</p><p>那麼就得找來老司機 Event bus 來幫忙了！</p><p><img src="/static/img/vue-component-data-transfer/event-bus.png"></p><p>那怎麼使用 Event bus 呢？ 很簡單，我們新增一個變數，並指定一個新的 Vue 物件實體給它：</p><pre><code class="javascript">// event busvar bus = new Vue();</code></pre><p>沒開玩笑，就這樣。 不相信？ 好，讓我們再回到前面的計數器範例。</p><p>前面做好的計數器看起來很完美，但總覺得好像缺少了什麼東西對吧... 是 reset 啊，我忘了 reset ！ 沒錯，如果沒有 reset 按鈕，如果要重新計算的話不就每次都得重整頁面，實在太 low 了。</p><p>沒問題，我們這就新增一顆 reset 按鈕：</p><pre><code class="html">  &lt;div id=&quot;app&quot;&gt;    &lt;h1&gt;Total: {{sum}}&lt;/h1&gt;    &lt;button-counter @add-sum=&quot;sum++&quot;&gt;&lt;/button-counter&gt;    &lt;button-counter @add-sum=&quot;sum++&quot;&gt;&lt;/button-counter&gt;    &lt;button&gt;reset&lt;/button&gt;  &lt;/div&gt;</code></pre><p>但問題來了，我要怎麼在這個 reset 按鈕按下去的同時，針對所有的計數器與頂層的 sum 來歸零呢？</p><p>此時輪到老司機出場了！</p><p>首先，我們先將 reset button 封裝成一個獨立的元件 <code>&lt;button-reset&gt;</code>：</p><pre><code class="html">  &lt;div id=&quot;app&quot;&gt;    &lt;h1&gt;Total: {{sum}}&lt;/h1&gt;    &lt;button-counter @add-sum=&quot;sum++&quot;&gt;&lt;/button-counter&gt;    &lt;button-counter @add-sum=&quot;sum++&quot;&gt;&lt;/button-counter&gt;    &lt;button-reset&gt;&lt;/button-reset&gt;  &lt;/div&gt;</code></pre><p>然後是 JS 的部分，一開始先新增一個 Vue 實體物件來當作 Event Bus：</p><pre><code class="javascript">var bus = new Vue();</code></pre><p>我們在原本的 <code>&lt;button-counter&gt;</code> 與頂層實體 <code>#app</code> 分別加上 <code>reset</code> 這個 method，當各自的 <code>reset</code> 被呼叫的時候，所屬的 <code>count</code> 與 <code>sum</code> 就歸零。</p><pre><code class="javascript">Vue.component(&#39;button-counter&#39;, {  props: [&#39;initialCounter&#39;],  data: function () {    return {      count: this.initialCounter || 0    }  },  template: &#39;&lt;button @click=&quot;count++; $emit(\&#39;add-sum\&#39;)&quot;&gt;You clicked me {{ count }} times.&lt;/button&gt;&#39;,  methods: {    reset: function(){      this.count = 0;    }  },  created: function(){    bus.$on(&#39;reset&#39;, this.reset);  }});var app = new Vue({  el: &#39;#app&#39;,  data: {    sum: 0,  },  methods: {    reset: function(){      this.sum = 0;    }  },  created: function(){    bus.$on(&#39;reset&#39;, this.reset);  }});</code></pre><p>然後，我們在元件的 <code>created</code> 階段分別為 <code>bus</code> 註冊了 <code>reset</code> 事件： <code>bus.$on(&#39;reset&#39;, this.reset);</code> ，意思是說，當 <code>bus</code> 觸發了 <code>reset</code> 這個事件的時候，就會去呼叫實體內的 <code>reset</code> method。</p><p>當然，事件註冊之後，總要有個地方觸發它，這個任務就交給後來新增的 <code>&lt;button-reset&gt;</code> 吧。</p><pre><code class="javascript">Vue.component(&#39;button-reset&#39;, {  template: &#39;&lt;button @click=&quot;reset&quot;&gt;reset&lt;/button&gt;&#39;,  methods: {    reset: function(){      bus.$emit(&#39;reset&#39;);    }  }});</code></pre><p>可以看到 <code>&lt;button-reset&gt;</code> 的內部構造其實很簡單，當 reset 按鈕被點擊的時候，就向 <code>bus</code> 去發送 <code>reset</code> 這個事件。</p><p>然後接下來的事情就交給老司機 <code>bus</code> 去處理啦！</p><p><img src="/static/img/vue-component-data-transfer/reset.gif"><a href="https://jsbin.com/lozivij/1/edit?html,js,output" target="_blank" rel="noopener">Demo link</a></p><p>雖然 Event Bus 很方便，但需要注意的小地方也不少，例如小心<strong>事件命名的衝突</strong>，以及在元件銷毀 (beforeDestroy) 前，要記得自行透過 <code>$off</code> 清除所監聽的所有事件，如：</p><pre><code class="js">Vue.component(&#39;button-counter&#39;, {  // 略  created: function(){    bus.$on(&#39;reset&#39;, this.reset);  },  beforeDestroy: function(){    bus.$off(&#39;reset&#39;, this.reset);  }});</code></pre><hr><h2 id="全域狀態的管理"><a href="#全域狀態的管理" class="headerlink" title="全域狀態的管理"></a>全域狀態的管理</h2><p>這部分其實跟資料傳遞比較沒有關係，要講的是狀態管理的策略。前面提到每個元件都有自己獨立的 scope，但是當某個資料 (或狀態) 需要在多處被引用的時候，光靠 Props / Events 其實是不好維護的。</p><p>做法有很多種，但概念都是一樣的。</p><p>像是在開發階段，很多人會設定環境變數來區別「開發模式」或是「線上模式」，像這樣：</p><pre><code class="js">var hostName = (Vue.config.productionTip) ? &#39;http://localhost:3000&#39; : &#39;https://www.your-api.com&#39;;</code></pre><p>但是否代表我們在每個 component 的 <code>created</code> 階段就必須跑一次這段程式呢？如果是初始後就不會被改變的值，我們可以這樣做：</p><pre><code class="js">Vue.config.productionTip = false;Vue.prototype.$hostname = (Vue.config.productionTip) ? &#39;http://localhost:3000&#39; : &#39;https://www.your-api.com&#39;;</code></pre><p>透過 <code>Vue.prototype</code> 來定義 <code>$hostname</code>，然後在 component 裡面可以透過 <code>this.$hostname</code> 來取用：</p><pre><code class="js">Vue.component(&#39;custom-component&#39;, {  created: function(){    console.log(this.$hostname);  }});</code></pre><p>或者如果你不希望每個 Vue 元件實體都被建立這份資料，那也可以改用 mixins 的方式，在你想要設定的元件上加入：</p><pre><code class="js">// define a mixin objectvar myMixin = {  data: function(){    Vue.config.productionTip = false;    return{      hostname: (Vue.config.productionTip) ? &#39;http://localhost:3000&#39; : &#39;https://www.your-api.com&#39;    }  }};Vue.component(&#39;custom-component&#39;, {  mixins: [myMixin],  created: function(){    console.log(this.hostname);  }});</code></pre><p>當然，透過 mixin 所生成的 data 在建立之後，每個元件的 data 就都是獨立的了。 以上面的例子來說，當你改了某個元件的 <code>hostname</code> 並不會影響到另一個元件的 <code>hostname</code>。</p><p>當然 mixins 也有 <a href="https://vuejs.org/v2/guide/mixins.html#Global-Mixin" target="_blank" rel="noopener">global mixin</a> 的寫法，但就要小心這樣的用法會影響的可是所有引入的元件 (包括 third-party component) 需要特別注意。</p><h2 id="真相只有一個：-SSOT-Single-Source-of-Truth"><a href="#真相只有一個：-SSOT-Single-Source-of-Truth" class="headerlink" title="真相只有一個： SSOT - Single Source of Truth"></a>真相只有一個： SSOT - Single Source of Truth</h2><p>前面講的是元件引入外部全域資料的做法，最後來講講元件之間資料共享的部分。</p><p>為什麼會需要資料共享？ 先看個例子。</p><hr><p>近年出國的人很多，尤其是日本，所以現在我們想要做個幣值轉換器，來計算台幣與日幣的轉換。 假設 1 日幣 = 0.278 台幣，那麼我們也許可以這樣開始寫：</p><pre><code class="html">  &lt;div id=&quot;app&quot;&gt;    &lt;p&gt;1 日幣 = 0.278 台幣&lt;/p&gt;    &lt;div&gt;台幣 &lt;input type=&quot;text&quot; v-model=&quot;twd&quot;&gt;&lt;/div&gt;    &lt;div&gt;日幣 &lt;input type=&quot;text&quot; v-model=&quot;jpy&quot;&gt;&lt;/div&gt;  &lt;/div&gt;</code></pre><pre><code class="javascript">  var app = new Vue({    el: &#39;#app&#39;,    data: {     twd: 0.278,     jpy: 1,    }  });</code></pre><p><img src="/static/img/vue-component-data-transfer/ssot-1.png"></p><p>當然現在還沒有任何功能，我們只是把兩個欄位分別與 data 的 <code>twd</code> 與 <code>jpy</code> 做綁定。 接下來要開始做連動功能，所以我們加上 <code>twd2jpy</code> 與 <code>jpy2twd</code> 兩個 method 來計算。</p><p>像這樣，我們分別為兩個輸入框加上 input 事件，並觸發 <code>twd2jpy</code> 或 <code>jpy2twd</code> 來幫我們做計算：</p><pre><code class="html">  &lt;div id=&quot;app&quot;&gt;    &lt;p&gt;1 日幣 = 0.278 台幣&lt;/p&gt;    &lt;div&gt;台幣 &lt;input type=&quot;text&quot; v-model=&quot;twd&quot; @input=&quot;twd2jpy&quot;&gt;&lt;/div&gt;    &lt;div&gt;日幣 &lt;input type=&quot;text&quot; v-model=&quot;jpy&quot; @input=&quot;jpy2twd&quot;&gt;&lt;/div&gt;  &lt;/div&gt;</code></pre><pre><code class="javascript">var app = new Vue({  el: &#39;#app&#39;,  data: {     twd: 0.278,     jpy: 1,  },  methods: {    twd2jpy: function(){      this.jpy = Number.parseFloat(Number(this.twd) / 0.278).toFixed(3);    },    jpy2twd: function(){      this.twd = Number.parseFloat(Number(this.jpy) * 0.278).toFixed(3);    },  }});</code></pre><p><img src="/static/img/vue-component-data-transfer/ssot-1.gif"><a href="https://jsbin.com/tirunex/1/edit?html,js,output" target="_blank" rel="noopener">Demo link</a></p><p>喔喔，看起來真的是好棒棒啊！ 果然 Vue 就是神！ 我們又完成了一項艱難的任務！</p><p><strong style="font-size: 22px;">等等！</strong></p><p>從遠方看到 PM 又走來了 (謎之聲：<del>哪來的 PM </del>) ，並問說「哇這麼厲害，那可不可以也幫我加上美金、人民幣的轉換啊」</p><p>程序猿心想，如果要加上人民幣與美金的話，那就還得再加入 <code>twd2usd</code>、<code>twd2rmb</code>、<code>jpy2usd</code>、<code>jpy2rmb</code>、<code>usd2twd</code>、<code>usd2jpy</code>、<code>usd2rmb</code> ......</p><p>(程序猿吐血而亡)</p><hr><p>看到這裡，相信你已經發現問題出在哪了吧！</p><p>在做幣值的計算，其實不管台幣換日幣，或是日幣換台幣，從一開始我們就只需要一種基準值，不管怎麼換，錢都是一樣的。</p><p><img src="/static/img/vue-component-data-transfer/state.png"></p><p>換言之，程式可以改寫成這樣：</p><pre><code class="html">  &lt;div id=&quot;app&quot;&gt;    &lt;p&gt;1 日幣 = 0.278 台幣&lt;/p&gt;    &lt;div&gt;台幣 &lt;input type=&quot;text&quot; v-model=&quot;twd&quot;&gt;&lt;/div&gt;    &lt;div&gt;日幣 &lt;input type=&quot;text&quot; v-model=&quot;jpy&quot;&gt;&lt;/div&gt;  &lt;/div&gt;</code></pre><pre><code>var app = new Vue({  el: &#39;#app&#39;,  data: {     twd: 0.278 ,  },  computed: {    jpy:{      get: function(){        return Number.parseFloat(Number(this.twd) / 0.278).toFixed(3);      },      set: function(val){        this.twd = Number.parseFloat(Number(val) * 0.278).toFixed(3);      }    }  }});</code></pre><p>此時 data 只留下 <code>twd</code> 作為基準， <code>jpy</code> 則是透過 computed 來依賴 <code>twd</code> 計算，並在 <code>jpy</code> 更新的時候，透過 <code>set</code> 去改寫 <code>twd</code> 的數值。</p><p><a href="https://jsbin.com/mokahev/1/edit?html,js,output" target="_blank" rel="noopener">Demo link</a></p><p>往後，就算我們要加上美金也只需要在 <code>computed</code> 屬性加上 <code>usd</code>：</p><pre><code>var app = new Vue({  el: &#39;#app&#39;,  data: {     twd: 1,  },  computed: {    jpy: {      get: function(){        return Number.parseFloat(Number(this.twd) / 0.278).toFixed(3);      },      set: function(val){        this.twd = Number.parseFloat(Number(val) * 0.278).toFixed(3);      }    },    usd: {      get: function(){        return Number.parseFloat(Number(this.twd) / 30.645).toFixed(3);      },      set: function(val){        this.twd = Number.parseFloat(Number(val) * 30.645).toFixed(3);      }    }  }});</code></pre><p><img src="/static/img/vue-component-data-transfer/ssot-2.gif"><a href="https://jsbin.com/jovopux/1/edit?html,js,output" target="_blank" rel="noopener">Demo link</a></p><p>相信未來若要再加入其他幣值的計算，肯定也不是難事了！</p><hr><p>從以上的範例當中，我們可以看到 SSOT (Single Source of Truth) 其實只是一種開發準則、概念性的東西，順著這個邏輯再往下延伸，相信一定會有朋友會想到 <a href="https://facebook.github.io/flux/" target="_blank" rel="noopener">Flux</a> 或是 <a href="https://github.com/vuejs/vuex" target="_blank" rel="noopener">Vuex</a> 的架構。</p><p>沒錯，在 Vue 開發大型應用架構確實用 Vuex 來做集中式狀態管理是相當實用的，礙於篇章的關係，網路上的相關介紹與教學文件也不少，就請有興趣的朋友自行搜尋囉。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;img src=&quot;/static/img/vue-instance.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;由於 VueJS 採用元件系統 (Component System) 來組織我們的應用程式，元件之間的資料傳遞，一直都是個不容忽視的問題，尤其在過去我們看過太多資料傳遞不當處理的方式，專案隨著時間不斷擴張，變得難以維護，最終導致不得不砍掉重練的悲劇。&lt;/p&gt;
    
    </summary>
    
      <category term="vue.js" scheme="https://kuro.tw/categories/vue-js/"/>
    
    
      <category term="vue.js" scheme="https://kuro.tw/tags/vue-js/"/>
    
  </entry>
  
  <entry>
    <title>初探 Vue-CLI v3.0</title>
    <link href="https://kuro.tw/posts/2018/07/04/%E5%88%9D%E6%8E%A2-Vue-CLI-3-0/"/>
    <id>https://kuro.tw/posts/2018/07/04/初探-Vue-CLI-3-0/</id>
    <published>2018-07-04T09:21:00.000Z</published>
    <updated>2019-12-06T11:25:16.213Z</updated>
    
    <content type="html"><![CDATA[<p>相信不管有沒有用過 Vue 開發的朋友，應該都曾聽過 <a href="https://cli.vuejs.org" target="_blank" rel="noopener">Vue CLI</a> 這個用來快速建置 Vue 專案的工具。 <a href="https://cli.vuejs.org" target="_blank" rel="noopener">Vue CLI</a> 這個工具套件現在即將推出 3.0 的版本，當然也有了不少變革與進展，這篇文章就簡單來介紹 Vue-CLI v3 提供了什麼新功能吧。</p><p><img src="/static/img/vue-cli3/cli-logo.png" alt="Vue-CLI 官網: https://cli.vuejs.org"></p><a id="more"></a><h2 id="安裝"><a href="#安裝" class="headerlink" title="安裝"></a>安裝</h2><p>Vue CLI v3 要求 Node.js V8 或更高版本（建議版本 8.10.0+ ）。</p><p>可以使用 npm 或是 yarn 來進行安裝。</p><pre><code>$ npm install -g @vue/cli</code></pre><p>或</p><pre><code>$ yarn global add @vue/cli</code></pre><p>安裝後可以透過 <code>vue -V</code> 指令來確認 Vue-CLI 的版本：</p><pre><code>$ vue -V3.0.0-rc.3</code></pre><p>註: 本文撰寫時最新版本為 <strong>3.0.0-rc.3</strong>。</p><h2 id="建置新的-Vue-專案"><a href="#建置新的-Vue-專案" class="headerlink" title="建置新的 Vue 專案"></a>建置新的 Vue 專案</h2><p>過去建立新專案，是透過 <code>vue init</code> 再加上樣板名稱，但自從 CLI v3 後改直接執行 <code>vue create</code> 指令建立，如：</p><pre><code>vue create hello-world</code></pre><p>首先可以選擇最基本的 Babel + ESLint 預設選項：</p><p><img src="/static/img/vue-cli3/install.png" alt></p><p>或者也可以改選 &quot;Manually select features&quot; 來設定你需要安裝的部分。</p><p><img src="/static/img/vue-cli3/Manually.png" alt></p><p>接著會詢問你要使用 Yarn 或 NPM 來管理/安裝套件：</p><p><img src="/static/img/vue-cli3/install2.png" alt></p><p>然後是一連串的安裝過程，完成時會跟過去一樣，提示你相關的執行指令：</p><pre><code> $ cd hello-world $ yarn serve</code></pre><p><img src="/static/img/vue-cli3/install3.png" alt></p><p>馬上來執行看看</p><p><img src="/static/img/vue-cli3/serve.png" alt></p><p>vue-cli 會建立一個 http 服務，你可以從 localhost 或是遠端連進。然後打開瀏覽器：</p><p><img src="/static/img/vue-cli3/hello.png" alt></p><p>跟過去不太一樣的是，在 CLI v3 後，啟動測試環境的指令改為 <code>yarn serve</code> 而不再是過去的 <code>yarn dev</code> 了。 當然背後仍是透過 npm script 來執行，我們在 package.json 可以看到實際上是執行 <code>vue-cli-service</code> 這個服務。</p><p><img src="/static/img/vue-cli3/package.png" alt></p><hr><p>同樣地，如果我們要打包發佈時，執行 <code>yarn build</code> ， Vue CLI 就會幫我們把相關的程式碼打包至 <code>dist</code> 目錄了。 像這樣：</p><p><img src="/static/img/vue-cli3/build.png" alt></p><h2 id="vue-cli-service"><a href="#vue-cli-service" class="headerlink" title="vue-cli-service"></a>vue-cli-service</h2><p><strong>vue-cli-service serve</strong> 提供了下面幾個選項來幫助我們啟動測試的 server:</p><pre><code>Usage: vue-cli-service serve [options]Options:  --open    open browser on server start  --copy    copy url to clipboard on server start  --mode    specify env mode (default: development)  --host    specify host (default: 0.0.0.0)  --port    specify port (default: 8080)  --https   use https (default: false)</code></pre><p>如果對 <strong>vue-cli-service</strong> 有興趣的朋友可以詳閱官方文件： <a href="https://cli.vuejs.org/guide/cli-service.html" target="_blank" rel="noopener">https://cli.vuejs.org/guide/cli-service.html</a></p><h2 id="加入-CSS-Pre-Processors"><a href="#加入-CSS-Pre-Processors" class="headerlink" title="加入 CSS Pre-Processors"></a>加入 CSS Pre-Processors</h2><p>CLI v3 後預設不安裝任何的 CSS Pre-Processors (預處理器)，不過我們還是可以自行挑選喜歡的來使用。</p><pre><code># Sassnpm install -D sass-loader node-sass# 或 (yarn)yarn add sass-loader node-sass# Lessnpm install -D less-loader less# 或 (yarn)yarn add less-loader less# Stylusnpm install -D stylus-loader stylus# 或 (yarn)yarn add stylus-loader stylus</code></pre><p>安裝後即可在對應的 .vue 檔案中指定 <code>lang</code> 屬性即可，這裡以 <code>scss</code> 為例：</p><pre><code>&lt;style lang=&quot;scss&quot;&gt;$color = red;&lt;/style&gt;</code></pre><h2 id="vue-config-js-設定"><a href="#vue-config-js-設定" class="headerlink" title="vue.config.js 設定"></a>vue.config.js 設定</h2><p>專案建置後，若你仔細觀察就會發現 Vue CLI v3 將原本 webpack 的設定檔案完全隱藏，在目錄中完全找不到 webpack 的 config 檔了：</p><p><img src="/static/img/vue-cli3/project.png" alt></p><p>取而代之的是，若我們需要自行調整 webpack 的設定時，需另外在專案的根目錄建立 vue.config.js 來設定這些細節 (當然也可以不用，那就是採預設值) ：</p><pre><code class="javascript">// vue.config.jsmodule.exports = {  baseUrl: &#39;/&#39;,  outputDir: &#39;dist&#39;,  lintOnSave: true,  compiler: false,  chainWebpack: () =&gt; {},  configureWebpack: () =&gt; {},  devServer: {    // 略...  },  // ......};</code></pre><hr><h2 id="透過-vue-ui-來建置-vue-專案"><a href="#透過-vue-ui-來建置-vue-專案" class="headerlink" title="透過 vue ui 來建置 vue 專案"></a>透過 vue ui 來建置 vue 專案</h2><p>Vue CLI v3 提供的最大變革，是新增了圖形化介面來方便我們管理與建置專案。</p><p>你可以透過執行 <code>vue ui</code> 這個指令，此時會在 localhost 建立一個 http 服務，並且透過瀏覽器開啟管理介面：</p><p><img src="/static/img/vue-cli3/vue-ui.png" alt></p><p>有興趣的朋友可以自行試用。</p><h2 id="執行舊版的-Vue-CLI"><a href="#執行舊版的-Vue-CLI" class="headerlink" title="執行舊版的 Vue CLI"></a>執行舊版的 Vue CLI</h2><p>當你安裝 CLIv3 後，預設會覆蓋掉過去舊版的 Vue CLI。若你還需要過去的 CLI 樣板功能，可以另外安裝 <code>@vue/cli-init</code> 來執行 CLI 2.x 的版本：</p><pre><code>$ npm install -g @vue/cli-init# vue init now works exactly the same as vue-cli@2.x$ vue init webpack my-project</code></pre><p>以上就是新版 Vue-CLI v3 的簡單介紹，讓我們期待未來正式發佈的版本！ :)</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;相信不管有沒有用過 Vue 開發的朋友，應該都曾聽過 &lt;a href=&quot;https://cli.vuejs.org&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Vue CLI&lt;/a&gt; 這個用來快速建置 Vue 專案的工具。 &lt;a href=&quot;https://cli.vuejs.org&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Vue CLI&lt;/a&gt; 這個工具套件現在即將推出 3.0 的版本，當然也有了不少變革與進展，這篇文章就簡單來介紹 Vue-CLI v3 提供了什麼新功能吧。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/static/img/vue-cli3/cli-logo.png&quot; alt=&quot;Vue-CLI 官網: https://cli.vuejs.org&quot;&gt;&lt;/p&gt;
    
    </summary>
    
      <category term="vue.js" scheme="https://kuro.tw/categories/vue-js/"/>
    
    
      <category term="vue.js" scheme="https://kuro.tw/tags/vue-js/"/>
    
      <category term="yarn" scheme="https://kuro.tw/tags/yarn/"/>
    
  </entry>
  
  <entry>
    <title>寫在 IT 鐵人完賽之後</title>
    <link href="https://kuro.tw/posts/2018/01/02/%E5%AF%AB%E5%9C%A8-IT-%E9%90%B5%E4%BA%BA%E5%AE%8C%E8%B3%BD%E4%B9%8B%E5%BE%8C/"/>
    <id>https://kuro.tw/posts/2018/01/02/寫在-IT-鐵人完賽之後/</id>
    <published>2018-01-02T08:27:20.000Z</published>
    <updated>2019-12-06T11:25:53.533Z</updated>
    
    <content type="html"><![CDATA[<p>幾個月前在基於 <del>招生推廣</del> 社群分享，我寫了三篇關於 JavaScript 的 <code>this</code> 系列文，課程招生有沒有因此比較順利我是不知道，反正寫下來也當做自我複習。 三篇文章寫完後，貼到前端社群之後就被不少大大、前輩分享出去。 那時候才發現「<strong>喔，原來寫文章還真的有人會看</strong> ！」</p><a id="more"></a><p>本來想說有空可以再來寫點什麼</p><p><img src="/static/img/ithelp/fb.png" alt></p><p>結果馬上就有人推坑了 XD</p><p>然後看看行事曆，12月好像都是空著的呢，就來寫寫看吧。 還發了一篇超中二的主題說明:</p><p><img src="/static/img/ithelp/main.png" alt="&quot;重新認識 JavaScript&quot;"></p><p>這邊要說一下，本來很多人以為我要寫 VueJS 相關的題目，但要連續 Vue 30 天可能後面我會擠不出東西來，不如就把主題訂在 JavaScript 這種超大範圍的題目。 <del>要是真的沒東西講再把 Vue 拿出來湊版面 (大誤)</del></p><p>然後就是連續的三十天文章，傳送門在這： <a href="https://ithelp.ithome.com.tw/users/20065504/ironman/1259" target="_blank" rel="noopener">重新認識 JavaScript 系列</a></p><p>因為是本來就是自己熟悉的主題，再加上這些年來參加各種研討會、大小型社群聚會，可以拿出來用的素材還不少，寫起來其實不算有太大壓力。 比較有壓力的地方在於每天一早醒來就要開始先想好當天的標題，還有<strong>各種哏</strong>。 所以我滿佩服那些剛過 12 點就可以生出文章的朋友 XD</p><p>投稿的時候，老實說我根本沒想過這 30 篇文章要寫哪些東西，只有預先訂出大略範圍：</p><ul><li>JavaScript 歷史與基礎篇</li><li>瀏覽器裡的 JavaScript: BOM 與 DOM 篇</li><li>深入理解 JavaScript 核心篇</li><li>JavaScript 的現在與未來: ES20XX、前端框架與開發生態圈篇</li></ul><hr><p>接下來就是每天一篇的過程了。</p><p>中間當然也有想過要偷懶卡文一下。 但卡文這種事情，就跟欠卡債一樣，卡一天就會想卡第二天，而且我的文章篇幅其實還滿長的，只要卡一天我大概就不會想再補了 XD</p><p>另一方面是看到日漸增加的訂閱人數，因為死愛面子的個性，<strong>知道有人會看就不敢隨便亂寫</strong>，還好三十篇總算是順利寫完了。</p><p><img src="/static/img/ithelp/finished.png" alt></p><p>最後，感謝這段期間持續關注鐵人賽的朋友，希望我寫的東西對你們有幫助。 就這樣。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;幾個月前在基於 &lt;del&gt;招生推廣&lt;/del&gt; 社群分享，我寫了三篇關於 JavaScript 的 &lt;code&gt;this&lt;/code&gt; 系列文，課程招生有沒有因此比較順利我是不知道，反正寫下來也當做自我複習。 三篇文章寫完後，貼到前端社群之後就被不少大大、前輩分享出去。 那時候才發現「&lt;strong&gt;喔，原來寫文章還真的有人會看&lt;/strong&gt; ！」&lt;/p&gt;
    
    </summary>
    
      <category term="個人喇賽" scheme="https://kuro.tw/categories/%E5%80%8B%E4%BA%BA%E5%96%87%E8%B3%BD/"/>
    
    
      <category term="javascript" scheme="https://kuro.tw/tags/javascript/"/>
    
      <category term="重新認識 JavaScript" scheme="https://kuro.tw/tags/%E9%87%8D%E6%96%B0%E8%AA%8D%E8%AD%98-JavaScript/"/>
    
      <category term="IT鐵人賽" scheme="https://kuro.tw/tags/IT%E9%90%B5%E4%BA%BA%E8%B3%BD/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript 是「傳值」或「傳址」</title>
    <link href="https://kuro.tw/posts/2017/12/08/JavaScript-%E6%98%AF%E3%80%8C%E5%82%B3%E5%80%BC%E3%80%8D%E6%88%96%E3%80%8C%E5%82%B3%E5%9D%80%E3%80%8D/"/>
    <id>https://kuro.tw/posts/2017/12/08/JavaScript-是「傳值」或「傳址」/</id>
    <published>2017-12-08T11:29:49.000Z</published>
    <updated>2019-12-06T11:26:31.742Z</updated>
    
    <content type="html"><![CDATA[<p>JavaScript 基本型別 (Primitives) 內的資料，會是以<strong>純值</strong>的形式存在 ( <code>String</code>、<code>Number</code>、<code>Boolean</code>、<code>Null</code> 以及 <code>Undefined</code> )，而物件型別 (Object) 指的是可能由零或多種不同型別 (包括純值與物件) 所組合成的物件。</p><a id="more"></a><h2 id="基本型別"><a href="#基本型別" class="headerlink" title="基本型別"></a>基本型別</h2><p>當我們今天要給變數資料的時候，假設我們給兩個變數分別設定為 <code>10</code>：</p><pre><code class="javascript">var a = 10;var b = 10;// 在 JavaScript 判斷是否「相等」用 === console.log( a === b );      // true</code></pre><p>在基本型別的時候，會認為這兩個變數的「值」是相等的。 這應該不難理解，因為兩個變數的數值都是 <code>10</code>。同樣地，在字串的情況下也是：</p><pre><code class="javascript">var a = &#39;Kuro&#39;;var b = &#39;Kuro&#39;;var c = &#39;Jack&#39;;console.log( a === b );      // trueconsole.log( a === c );      // false</code></pre><p>所以在基本型別，當我們判斷這兩個變數是否相等，看的是裡面的內容，也就是「值」。</p><h2 id="物件型別"><a href="#物件型別" class="headerlink" title="物件型別"></a>物件型別</h2><p>在物件型別的狀況下就不同了。這裡我們分別宣告兩個物件，也都有個 <code>value</code> 的屬性。</p><pre><code class="javascript">var obj1 = { value: 10 };var obj2 = { value: 10 };</code></pre><p>猜猜看， <code>obj1 === obj2</code> 的結果會是？</p><p>. <br>. <br>. <br>. <br>. <br>. <br></p><p>答案會是 <code>false</code> 。  <del>想當然如果是 true 我就不用另外寫這篇了</del>剛接觸 JavaScript 的朋友可能無法理解這點，沒關係，我們繼續往下看。</p><p>在 JavaScript 的物件，我們可以把它看作是一個「實體 (instance)」，什麼意思呢，這裡我舉個例子。</p><p>假設我口袋裡有十塊錢，你口袋裡也有十塊錢。 <del>這樣我們就有二十塊錢（不是</del> </p><p>那麼在正常情況下，我們各自的十塊錢 <strong>可以買到的東西應該是一樣多的</strong> 對吧？ 這個時候，我可以說我們各自的十塊錢是「<strong>等值</strong>」的。 用程式碼來說，就像這樣：</p><pre><code class="javascript">var a = 10;var b = 10;console.log( a === b );      // true</code></pre><p>那麼在「物件」的情況下呢？剛剛說 JavaScript 的物件都應該看作是一個「<strong>實體</strong>」。</p><p>以「實體」的前提下，假設我在我口袋裡的十塊錢用麥克筆上面打個 <code>X</code>，<del>除非我是劉謙</del>，此時你口袋的十塊錢應該是不可能有 <code>X</code> 的記號對吧？</p><pre><code class="javascript">// 兩個 coin 的價值都是 10，但 coin1 與 coin2 卻不是同一個實體。var coin1 = { value: 10 };var coin2 = { value: 10 };console.log( coin1 === coin2 );      // false// 我在 coin1 畫了一個 Xcoin1.cross = true;// coin2.cross 當然不可能會有東西console.log( coin2.cross );          // undefined</code></pre><p>當然 JavaScript 的物件沒這麼單純，這裡暫時用極簡化的例子幫助各位理解。</p><h2 id="變數的更新與傳遞"><a href="#變數的更新與傳遞" class="headerlink" title="變數的更新與傳遞"></a>變數的更新與傳遞</h2><p>既然大家都知道，「變數」裡面的內容是可以被變動，那麼在理解了「基本型別」與「物件型別」在比較時的不同後，接著就來聊聊變數的更新與傳遞，這部分我們一樣分成「基本型別」與「物件型別」兩種來看。</p><h3 id="基本型別的更新與傳遞"><a href="#基本型別的更新與傳遞" class="headerlink" title="基本型別的更新與傳遞"></a>基本型別的更新與傳遞</h3><p>還記得十塊錢的範例嗎，如同稍早所說，在基本型別的變數中，我們看的是變數裡頭的「值」。 換言之，我們在複製變數的時候，複製的也是那個變數的「值」：</p><pre><code class="javascript">var a = 10;var b = a;console.log( a );   // 10console.log( b );   // 10</code></pre><p>可以看到，變數 <code>b</code> 的值是透過複製變數 <code>a</code> 的值而來。</p><p>但並不代表當變數 <code>a</code> 更新之後，會去影響變數 <code>b</code> 的數值：</p><pre><code class="javascript">a = 100;// 變數 b 依然是 10，而變數 a 變成了 100console.log( a );   // 100console.log( b );   // 10</code></pre><p>簡單來說， <code>var b = a;</code> 表面上看起來變數 <code>b</code> 的內容是透過複製變數 <code>a</code> 而來，但此時若變數 <code>a</code> 的內容為基本型別時，實際上變數 <code>b</code> 是去建立了一個新的值，然後將變數 <code>a</code> 的內容複製了一份過來。</p><p>這時候 <code>a</code> 與 <code>b</code> 各自是獨立的。</p><p>所以當變數 <code>a</code> 的內容後來經過更新變成 <code>100</code> 之後，變數 <code>b</code> 的內容依舊保持原來的 <code>10</code> 而不受影響。</p><p>像這種情況，我們通常會稱作「<strong>傳值</strong>」 (<strong>pass by value</strong>)。</p><h3 id="物件型別的更新與傳遞"><a href="#物件型別的更新與傳遞" class="headerlink" title="物件型別的更新與傳遞"></a>物件型別的更新與傳遞</h3><p>那麼換成了物件型別呢？讓我們回到剛剛 coin 的例子，並且稍微修改一下：</p><pre><code class="javascript">var coin1 = { value: 10 };var coin2 = coin1;console.log( coin1.value );       // 10console.log( coin2.value );       // 10</code></pre><p>乍看之下與前面基本型別 (純值) 的情況沒什麼不同，但是：</p><pre><code class="javascript">coin1.value = 100;console.log( coin1.value );       // 100console.log( coin2.value );       // 100</code></pre><p>當 <code>coin1.value</code> 的內容被更新了之後，連帶著 <code>coin2.value</code> 卻也跟著更新了。</p><p>而且此時，你透過 <code>===</code> 去檢查兩者實體時，會發現 <code>coin1</code> 與 <code>coin2</code> 實際上是同一個實體！</p><pre><code class="javascript">console.log( coin1 === coin2 );    // true</code></pre><p>聰明的你應該已經猜到，其實「物件」這類資料型態，在 JavaScript 中是透過「引用」的方式傳遞資料的。</p><p>什麼意思？ 這裡我用兩張圖來表示：</p><p><img src="https://ithelp.ithome.com.tw/upload/images/20171208/200655041RlwNOp0RU.png" alt="JavaScript Object 示意圖 - 1"></p><pre><code class="javascript">var coin1 = { value: 10 };</code></pre><p>首先我們建立起一個新的物件的時候，JavaScript 會在記憶體的某處建立起一個物件 (圖右側)，然後再將這個 <code>coin1</code> 變數指向新生成的物件。</p><p><img src="https://ithelp.ithome.com.tw/upload/images/20171208/200655046SEcyZbNfA.png" alt="JavaScript Object 示意圖 - 2"></p><pre><code class="javascript">var coin2 = coin1;</code></pre><p>接著，當我們宣告了第二個變數 <code>coin2</code> 之後，並且透過 <code>=</code> 將 <code>coin2</code> 指向 <code>coin1</code> 的位置。 於是我們更新了 <code>coin1.value</code> 的內容後， <code>coin2.value</code> 的內容也理所當然地被更新了。</p><pre><code class="javascript">coin1.value = 100;console.log( coin1.value );       // 100console.log( coin2.value );       // 100</code></pre><p>所以實際上可以看出，<code>coin1</code> 與 <code>coin2</code> 這兩個變數是指向同一個實體的。</p><p>像這種透過引用的方式來傳遞資料，接收的其實是引用的「參考」而不是值的副本時，我們通常會稱作「<strong>傳址</strong>」 (<strong>pass by reference</strong>)。</p><h2 id="JavaScript-是「傳值」或「傳址」？"><a href="#JavaScript-是「傳值」或「傳址」？" class="headerlink" title="JavaScript 是「傳值」或「傳址」？"></a>JavaScript 是「傳值」或「傳址」？</h2><p>回到主題，所以我說那個 JavaScript 是「傳值」或「傳址」呢？</p><p>在大多數的情況下，基本型別是「傳值」，而物件型別會是「傳址」的方式，但<strong>凡事都有例外</strong>。</p><p>我們來看看下面這個例子：</p><pre><code class="javascript">var coin1 = { value: 10 };function changeValue(obj) {  obj = { value: 123 };}changeValue(coin1);console.log(coin1);   // ？</code></pre><p>猜猜看，經過 <code>changeValue(coin1)</code> 操作後的 <code>coin1</code> 會是什麼？</p><p>答案仍是 <code>{ value: 10 }</code> 。</p><p>剛剛說過，物件型別會是「傳址」的方式來更新資料，那應該會是 <code>{ value: 123 }</code> 才對，為什麼依然不變？</p><p>事實上，JavaScript 不屬於單純的傳值或傳址。更準確一點來說，JavaScript 應該屬於透過 <strong>pass by sharing</strong> (還沒找到合適的中文翻譯) 來傳遞資料。</p><p>「傳值」或「傳址」對大多數的開發者來說應該都不陌生，那麼「<strong>pass by sharing</strong>」又是什麼呢？</p><h2 id="Pass-by-sharing"><a href="#Pass-by-sharing" class="headerlink" title="Pass by sharing"></a>Pass by sharing</h2><p>「Pass by sharing」的特點在於，當 <code>function</code> 的參數，如 <code>function changeValue(obj){ ... }</code> 中的 <code>obj</code> 被重新賦值的時候，外部變數的內容是不會被影響的。</p><pre><code class="javascript">var coin1 = { value: 10 };function changeValue(obj) {  obj = { value: 123 };}changeValue(coin1);console.log(coin1);   // 此時 coin1 仍是 { value: 10 }</code></pre><p>如果不是重新賦值的情況，則又會回到大家所熟悉的狀況：</p><pre><code class="javascript">var coin1 = { value: 10 };function changeValue(obj) {  // 僅更新 obj.value，並未重新賦值  obj.value = 123;}changeValue(coin1);console.log(coin1);   // 此時 coin1 則會變成 { value: 123 }</code></pre><h2 id="結論"><a href="#結論" class="headerlink" title="結論"></a>結論</h2><p>所以 JavaScript 到底屬於何種策略？ 我認為 JavaScript 應該更屬於 <strong>Pass by sharing</strong> 的形式。</p><p>參考 <a href="http://dmitrysoshnikov.com/ecmascript/chapter-8-evaluation-strategy/" target="_blank" rel="noopener">ECMA-262-3 in detail. Chapter 8. Evaluation strategy</a> 所說：</p><blockquote><p>Regardless of usage concept of reference in this case, this strategy should not be confused with the “call by reference” discussed above. <strong>The value of the argument is not a direct alias, but the copy of the address.</strong></p></blockquote><p>由於在 JavaScript 的物件類型是可變的 (mutable)，當物件更新時，會影響到所有引用這個物件的變數與其副本，修改時會變動到原本的參考，但當賦與新值時，會產生新的實體參考。</p><p>而基本型別則是不可變的 (immutable)，當你更新了某個基本型別的值時，與那個值的副本完全無關：</p><pre><code>var a = 10;var b = a;a = 100;console.log(a);     // 100console.log(b);     // 10</code></pre><p>這個時候在基本型別的操作下，以 Pass by sharing 的行為來說，與 Pass by value 的結果是完全一樣的，修改時永遠只能賦與新值。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;JavaScript 基本型別 (Primitives) 內的資料，會是以&lt;strong&gt;純值&lt;/strong&gt;的形式存在 ( &lt;code&gt;String&lt;/code&gt;、&lt;code&gt;Number&lt;/code&gt;、&lt;code&gt;Boolean&lt;/code&gt;、&lt;code&gt;Null&lt;/code&gt; 以及 &lt;code&gt;Undefined&lt;/code&gt; )，而物件型別 (Object) 指的是可能由零或多種不同型別 (包括純值與物件) 所組合成的物件。&lt;/p&gt;
    
    </summary>
    
      <category term="名為 JavaScript 的黑魔法" scheme="https://kuro.tw/categories/%E5%90%8D%E7%82%BA-JavaScript-%E7%9A%84%E9%BB%91%E9%AD%94%E6%B3%95/"/>
    
    
      <category term="javascript" scheme="https://kuro.tw/tags/javascript/"/>
    
      <category term="重新認識 JavaScript" scheme="https://kuro.tw/tags/%E9%87%8D%E6%96%B0%E8%AA%8D%E8%AD%98-JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>What&#39;s THIS in JavaScript ? [下]</title>
    <link href="https://kuro.tw/posts/2017/10/20/What-is-THIS-in-JavaScript-%E4%B8%8B/"/>
    <id>https://kuro.tw/posts/2017/10/20/What-is-THIS-in-JavaScript-下/</id>
    <published>2017-10-20T04:20:33.000Z</published>
    <updated>2019-12-06T11:28:50.820Z</updated>
    
    <content type="html"><![CDATA[<p>JavaScript 的 this 系列文終於來到最後一篇了，相信在前兩篇文章的說明下，各位對 <code>this</code> 應該有了基本的認識，而在這最後的篇幅中，我們將著重於 <strong>this 與前後文本 (context) 綁定的基本原則</strong> 且同時說明 <strong>如何決定 this 是誰的順序</strong>，期望各位在讀完這系列文章後，對於 <code>this</code> 所扮演的角色，能有更清楚、更深入的理解。</p><a id="more"></a><h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>這系列的主題其實是節錄自去年 (2016) 我在<a href="https://5xruby.tw/talks/JavaScript-this-01" target="_blank" rel="noopener">五倍紅寶石開設的課程</a>，講的是 「<strong>This</strong>」 在 JavaScript 這門程式語言裡所代表的各種面貌。 然而最近無論是社群還是課堂教學，發現仍有不少剛入門的朋友對 JavaScript 的 <code>This</code> 代表的意義不太熟悉，那麼我想整理出這幾篇文章也許可以釐清你對 <code>This</code> 的誤解，反正資料也都還在，不如就整理出來與大家分享順便做個紀錄。</p><p>系列文快速連結：</p><ul><li><a href="https://kuro.tw/posts/2017/10/12/What-is-THIS-in-JavaScript-%E4%B8%8A/">What&#39;s THIS in JavaScript ? [上]</a></li><li><a href="https://kuro.tw/posts/2017/10/17/What-s-THIS-in-JavaScript-%E4%B8%AD/">What&#39;s THIS in JavaScript ? [中]</a></li><li><a href="https://kuro.tw/posts/2017/10/20/What-is-THIS-in-JavaScript-%E4%B8%8B/">What&#39;s THIS in JavaScript ? [下]</a></li></ul><h2 id="this-與前後文本-context-綁定的基本原則"><a href="#this-與前後文本-context-綁定的基本原則" class="headerlink" title="this 與前後文本 (context) 綁定的基本原則"></a>this 與前後文本 (context) 綁定的基本原則</h2><p><code>this</code> 綁定的基本原則大致上可以分成下列四種：</p><ul><li>預設綁定 (Default Binding)</li><li>隱含式綁定 (Implicit Binding)</li><li>顯式綁定 (Explicit Binding)</li><li>「new」關鍵字綁定</li></ul><p><br></p><p>首先是 <strong>預設綁定 (Default Binding)</strong>。</p><p>宣告在全域範疇 (global scope) 的變數，與同名的全域物件 (window 或 global) 的屬性是一樣的意思。因為預設綁定的關係，當 function 是在普通、未經修飾的情況下被呼叫，也就是當 function 被呼叫的當下如果沒有值或是在 <code>func.call(null)</code> 或 <code>func.call(undefined)</code> 此類的情況下，此時裡面的 this 會自動指定至<strong>全域物件</strong>。</p><pre><code class="javascript">var a = 123;console.log( window.a );function foo(){  // this === window  console.log( this.a );}foo(); // 123</code></pre><p>雖然預設綁定規則會將 function 中的 this 預設指向全域物件，但同樣的情況，若是加上 <code>&quot;use strict&quot;</code> 宣告成嚴格模式後，原本預設將 <code>this</code> 綁定至全域物件的行爲，會轉變成 <code>undefined</code>。</p><pre><code class="javascript">var a = 123;console.log( window.a );function foo(){  &quot;use strict&quot;;  // this === undefined  console.log( this.a );}foo(); // TypeError</code></pre><p>接著來看看 <strong>隱含式綁定 (Implicit Binding)</strong>。</p><p>即使 function 被宣告的地方是在 global scope 中，只要它成為某個物件的參考屬性 (reference property)，在那個 function 被呼叫的當下，該 function 即被那個物件所包含。</p><pre><code class="js">function func() {  console.log( this.a );}var obj = {  a: 2,  foo: func};func();       // undefinedobj.foo();    // 2</code></pre><p>在上面的範例中可以看到，根據 「預設綁定」的原則，直接呼叫 <code>func()</code> 的情況下，此時的 <code>this.a</code> 實際上會指向 <code>window.a</code>，所以結果是 <code>undefined</code>。</p><p>而當我們在 <code>obj</code> 物件中，將 <code>foo</code> 這個屬性指到 <code>func()</code> 的時候，再透過 <code>obj</code> 來呼叫 <code>obj.foo()</code> 的時候，雖然實際上仍是 <code>func()</code> 被呼叫，但此時的 <code>this</code> 就會指向至 <code>obj</code> 這個 owner 的物件上，於是此時的 <code>this.a</code> 就會是 <code>obj.a</code> 也就是 2 。</p><p>理解了隱含式綁定的原則後，繼續來看看這個變化過的版本：</p><pre><code>function func() {  console.log( this.a );}var obj = {  a: 2,  foo: func};obj.foo();  // 2var func2 = obj.foo;func2();    // ??</code></pre><p>在稍早的說明中，我們已經知道 <code>obj.foo()</code> 的結果會是 2。此時，我們宣告另一個變數 <code>func2</code> 指向 <code>obj.foo</code>，那麼聰明的你是否可以猜到呼叫 <code>func2()</code> 的結果為何呢？</p><p>.<br>.<br>.<br>.<br>.<br>.<br></p><p>好，答案揭曉，是 <code>undefined</code> 。</p><p>先別急著翻桌，雖然 <code>func2</code> 看起來是對 <code>obj.foo</code> 的參考，但實際上 <code>func2</code> 參考的對象是 <code>window.func</code> 。</p><p>為什麼？</p><p>在本系列<a href="https://kuro.tw/posts/2017/10/12/What-is-THIS-in-JavaScript-%E4%B8%8A/">上篇</a>的時候，我們曾經說過，全域變數的上層物件是誰？ 就是 <code>window</code>。所以說宣告 <code>var func2 = obj.foo;</code> 的時候，實際上 <code>func2</code> 就是 <code>window.func2</code>，而你在執行 <code>func2()</code> 的時候，等同於執行 <code>window.func2()</code>，那麼此時的 <code>this</code> 就會是 <code>window</code>，而 <code>this.a</code> 自然就會是 <code>undefined</code>。</p><p>不信的話，你可以再宣告一個全域變數 <code>a</code> 並給定某個值之後再試著執行一次 <code>func2()</code> 來應證看看。</p><p>換句話說，決定 this 的關鍵<strong>不在於它屬於哪個物件，而是在於 function「呼叫的時機點」</strong>，當你透過物件呼叫某個方法 (method) 的時候，此時 <code>this</code> 就是那個物件 (owner object)。</p><p><br>再來是<strong>顯式綁定 (Explicit Binding)</strong>。</p><p>相較於前兩種，顯式綁定就單純許多，簡單來說就是透過 <code>.bind()</code> / <code>.call()</code> / <code>.apply()</code> 這類直接指定 <code>this</code> 的 function 都可被歸類至顯式綁定的類型。</p><p>像這樣：</p><pre><code class="js">function func() {  console.log( this.a );}var obj = {  a: 2};func();             // undefinedfunc.call(obj);     // 2</code></pre><p>關於  <code>.bind()</code> / <code>.call()</code> / <code>.apply()</code> 的介紹在本系列的 <a href="https://kuro.tw/posts/2017/10/17/What-s-THIS-in-JavaScript-%E4%B8%AD/">中篇</a> 已有不少篇幅說明，有興趣的朋友可直接點擊閱讀。</p><p>這裏值得一提的是，相信大家都已經知道，在巢狀函式的情況下 <code>this</code> 的結果會因為內部找不到 owner object 而被指向至 <code>window</code>，過去我們可以透過宣告另一個變數 (<code>that</code> 或 <code>self</code> 等) 來暫存指向原本的 <code>this</code>。</p><p>那麼，這裡要來介紹另一個小技巧「Hard binding」，透過 <code>.call()</code> 包裝，使得 function 不管在哪裡執行，其中的 <code>this</code> 都可以保持在我們所指定的那個物件上：</p><pre><code class="js">function func() {  console.log( this.a );}var obj = {  a: 2};var obj2 = {  a: 100};// Hard binding functionvar hard_binding_func = function() {  func.call( obj );};func();                 // undefinedfunc.call(obj);         // 2func.call(obj2);        // 100hard_binding_func();    // 2hard_binding_func.call(obj2);  // 2window.setTimeout( func, 10);  // undefinedwindow.setTimeout( hard_binding_func, 10);  // 2</code></pre><p>在上面的範例中，我們透過一個 <code>hard_binding_func</code> 的 function 來把 <code>func.call( obj )</code> 包裝起來，於是此時不管 <code>hard_binding_func</code> 在哪裡執行，都不再受外層的 <code>this</code> 變化所影響，因為包裝後 <code>func</code> 的 <code>this</code> 已經被鎖定在 <code>obj</code> 上了，所以始終都是維持在 <code>obj.a</code> 也就是 2 的結果，這類的處理技巧就叫做「Hard binding」。</p><p>當然，自從 ES5 有了 <code>.bind()</code> 之後，剛剛的寫法甚至可以寫成這樣：</p><pre><code class="js">// Hard binding functionvar hard_binding_func = func.bind(obj);</code></pre><p>執行的結果都是一樣的。<br></p><p>最後一個是<strong>「new」關鍵字綁定</strong>。</p><p>在傳統類別導向 (class-oriented) 的程式語言中，建構子 (constructors) 是被附接到類別上的特殊方法，在透過 <code>new</code> 將 class 實體化的時候，這個建構子方法就會被呼叫。 而 JavaScript 雖然也有 <code>new</code> 這個關鍵字，運作時也與類別導向的語言行為類似，但由於 JavaScript 並不是一個類別導向的程式語言，所以它的 <code>new</code> 運作原理並不相同。</p><p>當一個 function 前面帶有 <code>new</code> 被呼叫時，會發生：</p><ul><li>會產生一個新的物件 (物件被建構出來)</li><li>這個新建構的物件會被設為那個 function 的 <code>this</code> 綁定目標，也就是 <code>this</code> 會指向新建構的物件。</li><li>除非這個 function 指定回傳 (return) 了他自己的替代物件，否則這個透過 <code>new</code> 產生的物件會被自動回傳。</li></ul><pre><code class="js">function foo(a) {  this.a = a;}var obj = new foo( 123 );console.log( obj.a );      // 123</code></pre><p>在上面的範例中，因為呼叫 <code>foo</code> 時，加了一個 <code>new</code> ，所以建構了一個新物件，並回傳到 <code>obj</code>。透過傳入的參數 123，在建立物件的時候，會作為新物件的屬性 <code>a</code> 的值，這種用 <code>new</code> 建立 <code>this</code> 綁定的方式，就是 <code>new</code> 關鍵字綁定的方式。</p><h2 id="this-綁定的優先順序"><a href="#this-綁定的優先順序" class="headerlink" title="this 綁定的優先順序"></a>this 綁定的優先順序</h2><p>看完了 <code>this</code> 與前後文本 (context) 綁定的基本原則後，接著我們來看看 <code>this</code> 綁定的優先順序。</p><p>當「隱含式綁定」與「顯式綁定」衝突時，此時 <code>this</code> 會以「顯式綁定」為主：</p><pre><code class="js">function func() {  console.log( this.a );}var obj1 = { a: 2, foo: func };var obj2 = { a: 3, foo: func };// 隱含式綁定obj1.foo();  // 2obj2.foo();  // 3// 顯式綁定obj1.foo.call( obj2 );  // 3obj2.foo.call( obj1 );  // 2</code></pre><pre><code class="js">function func(something) {  this.a = something;}var obj1 = { foo: func };var obj2 = {};// 以參數帶入，則 this 為物件本身obj1.foo( 2 );console.log( obj1.a );    // 2// 透過 call 強至指定 thisobj1.foo.call( obj2, 3 );console.log( obj2.a );    // 3// this = 建構子所產生的物件var bar = new obj1.foo( 4 );console.log( obj1.a );    // 2console.log( bar.a );     // 4</code></pre><h2 id="總結"><a href="#總結" class="headerlink" title="總結"></a>總結</h2><p>綜合上述範例介紹，我們可以簡單總結出一個結論：</p><ul><li>這個 function 的呼叫，是透過 <code>new</code> 進行的嗎？ 如果是，那 <code>this</code> 就是被建構出來的物件。</li><li>這個 function 是以 <code>.call()</code> 或 <code>.apply()</code> 的方式呼叫的嗎？ 或是 function 透過 <code>.bind()</code> 指定？ 如果是，那 <code>this</code> 就是被指定的物件。</li><li>這個 function 被呼叫時，是否存在於某個物件？ 如果是，那 <code>this</code> 就是那個物件。</li><li>如果沒有滿足以上條件，則此 function 裡的 <code>this</code> 就一定是全域物件: <code>window</code> 或是 <code>global</code>，在嚴格模式下則是 <code>undefined</code>。</li></ul><p>而決定 this 是誰的關鍵：</p><ul><li>function 可以透過 <code>.bind()</code> 來指定 this 是誰。</li><li>當 function 透過 <code>call()</code> 或 <code>apply()</code> 來呼叫時， <code>this</code> 會指向第一個參數，且會立即被執行。</li><li>callback function 內的 <code>this</code> 會指向呼叫 callback function 的物件。</li><li>ES6 箭頭函數內建 <code>.bind()</code> 特性，此時 <code>this</code> 無法複寫。</li></ul><hr><p>關於 <code>this</code> 的介紹就在此告一段落。 相信在看完這系列關於 <code>this</code> 的介紹後，對於 <code>this</code> 在 JavaScript 這個程式語言所扮演的角色，能有更清楚、更深入的理解，針對系列文中若有任何疑問，也歡迎留言討論以及指正。</p><p>也希望這系列文章對還在學習 JavaScript 的你有所幫助。</p><p>最後再來小小工商一次，我在 <a href="https://5xruby.tw/" target="_blank" rel="noopener">五倍紅寶石</a> 所開設的 <a href="https://5xruby.tw/talks/JS-jQuery-2017-3" target="_blank" rel="noopener">JavaScript &amp; jQuery 前端開發入門實戰</a> 這門課程仍然持續開放報名中，不管你是想入門 JavaScript、對 jQuery 的開發應用有興趣，或是想要更進一步了解 JavaScript 以及 jQuery 在網站前端開發的技巧，都歡迎你來報名。 :)</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;JavaScript 的 this 系列文終於來到最後一篇了，相信在前兩篇文章的說明下，各位對 &lt;code&gt;this&lt;/code&gt; 應該有了基本的認識，而在這最後的篇幅中，我們將著重於 &lt;strong&gt;this 與前後文本 (context) 綁定的基本原則&lt;/strong&gt; 且同時說明 &lt;strong&gt;如何決定 this 是誰的順序&lt;/strong&gt;，期望各位在讀完這系列文章後，對於 &lt;code&gt;this&lt;/code&gt; 所扮演的角色，能有更清楚、更深入的理解。&lt;/p&gt;
    
    </summary>
    
      <category term="名為 JavaScript 的黑魔法" scheme="https://kuro.tw/categories/%E5%90%8D%E7%82%BA-JavaScript-%E7%9A%84%E9%BB%91%E9%AD%94%E6%B3%95/"/>
    
    
      <category term="javascript" scheme="https://kuro.tw/tags/javascript/"/>
    
      <category term="重新認識 JavaScript" scheme="https://kuro.tw/tags/%E9%87%8D%E6%96%B0%E8%AA%8D%E8%AD%98-JavaScript/"/>
    
      <category term="this" scheme="https://kuro.tw/tags/this/"/>
    
  </entry>
  
  <entry>
    <title>What&#39;s THIS in JavaScript ? [中]</title>
    <link href="https://kuro.tw/posts/2017/10/17/What-s-THIS-in-JavaScript-%E4%B8%AD/"/>
    <id>https://kuro.tw/posts/2017/10/17/What-s-THIS-in-JavaScript-中/</id>
    <published>2017-10-17T04:20:33.000Z</published>
    <updated>2019-12-06T11:28:53.310Z</updated>
    
    <content type="html"><![CDATA[<p>延續上個主題，上回我們提到了 <code>this</code> 是誰，是取決於 function 被呼叫的方式，這次我們繼續來談談 <code>this</code> 與 <code>function</code> 的關係，以及 function 中的 <code>.call()</code> 、 <code>.apply()</code> 與 <code>.bind()</code> 是如何對 <code>this</code> 來進行操作的。</p><a id="more"></a><h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>這系列的主題其實是節錄自去年 (2016) 我在<a href="https://5xruby.tw/talks/JavaScript-this-01" target="_blank" rel="noopener">五倍紅寶石開設的課程</a>，講的是 「<strong>This</strong>」 在 JavaScript 這門程式語言裡所代表的各種面貌。 然而最近無論是社群還是課堂教學，發現仍有不少剛入門的朋友對 JavaScript 的 <code>This</code> 代表的意義不太熟悉，那麼我想整理出這幾篇文章也許可以釐清你對 <code>This</code> 的誤解，反正資料也都還在，不如就整理出來與大家分享順便做個紀錄。</p><p>系列文快速連結：</p><ul><li><a href="https://kuro.tw/posts/2017/10/12/What-is-THIS-in-JavaScript-%E4%B8%8A/">What&#39;s THIS in JavaScript ? [上]</a></li><li><a href="https://kuro.tw/posts/2017/10/17/What-s-THIS-in-JavaScript-%E4%B8%AD/">What&#39;s THIS in JavaScript ? [中]</a></li><li><a href="https://kuro.tw/posts/2017/10/20/What-is-THIS-in-JavaScript-%E4%B8%8B/">What&#39;s THIS in JavaScript ? [下]</a></li></ul><h2 id="quot-THIS-quot-or-quot-THAT-quot-？"><a href="#quot-THIS-quot-or-quot-THAT-quot-？" class="headerlink" title="&quot;THIS&quot; or &quot;THAT&quot; ？"></a>&quot;THIS&quot; or &quot;THAT&quot; ？</h2><p>相信大部分朋友第一次接觸 <code>this</code> 的時候，應該都是在處理「事件」(event) 的綁定吧？</p><p>當我們要取得觸發事件的元素時，如 <code>click</code> ， 這時候我們就會透過 <code>this</code> 來取得：</p><h4 id="HTML"><a href="#HTML" class="headerlink" title="HTML"></a>HTML</h4><pre><code class="html">&lt;button id=&quot;btn&quot;&gt;按我&lt;/button&gt;</code></pre><h4 id="JavaScript"><a href="#JavaScript" class="headerlink" title="JavaScript"></a>JavaScript</h4><pre><code class="javascript">var el = document.getElementById(&quot;btn&quot;);el.addEventListener(&quot;click&quot;, function(event) {  console.log( this.textContent );}, false);</code></pre><p>像上面這樣，我們就可以透過 <code>this.textContent</code> 來取得觸發 <code>click</code> 事件的 <code>&lt;button&gt;</code> 裡面的文字。</p><p>雖然我們可以在執行事件的 callback function 裡面透過 <code>this</code> 來取得觸發事件的元素，但偶爾也會遇到像這樣的例子：</p><pre><code class="javascript">// ajax functionvar $ajax = function(url, callback) {  var request = new XMLHttpRequest();  request.open(&#39;GET&#39;, url, true);  request.onload = function() {    if (request.status &gt;= 200 &amp;&amp; request.status &lt; 400) {      var data = JSON.parse(request.responseText);      if (typeof(callback) === &#39;function&#39;) {        callback.call(null, data);      }    }  };  request.send();};// button &amp; click event.var el = document.getElementById(&quot;btn&quot;);// 按下按鈕後執行 ajax，但在 callback function 的 this 卻不是你想像中的那樣el.addEventListener(&quot;click&quot;, function(event) {  console.log( this.textContent );  $ajax(&#39;[URL]&#39;, function(res) {    // this.textContent =&gt; undefined    console.log(this.textContent, res);  });}, false);</code></pre><p>像這種時候，最簡單的解法就是用另一個變數來對 <code>this</code> 做參考，像這樣：</p><pre><code class="js">el.addEventListener(&quot;click&quot;, function(event) {  // 透過 that 參考  var that = this;  console.log( this.textContent );  $ajax(&#39;[URL]&#39;, function(res) {    // this.textContent =&gt; undefined    console.log(that.textContent, res);  });}, false);</code></pre><p>上面這個例子透過 <code>that</code> 這個變數來指向原本的 <code>this</code>，於是就可以在 callback function 裡的 scope 取得原本的 <code>this.textContent</code> 了。</p><h2 id="強制指定-this-的方式"><a href="#強制指定-this-的方式" class="headerlink" title="強制指定 this 的方式"></a>強制指定 this 的方式</h2><p>透過另一個變數來暫存 <code>this</code> 的方式雖然方便，那麼有沒有其他方式可以取得原本 <code>this</code> 的內容呢？</p><p>首先延續上一個範例，我們先看 <code>bind()</code>。在上個範例中，我們用 <code>that</code> 這個變數來替代 <code>this</code>，以便取得觸發 <code>click</code> 事件的 <code>el</code>：</p><pre><code class="js">el.addEventListener(&quot;click&quot;, function(event) {  // 透過 that 參考  var that = this;  console.log( this.textContent );  $ajax(&#39;[URL]&#39;, function(res) {    // this.textContent =&gt; undefined    console.log(that.textContent, res);  });}, false);</code></pre><p>如果用 <code>bind()</code> 改寫的話，可以像這樣：</p><pre><code class="js">el.addEventListener(&quot;click&quot;, function(event) {  console.log( this.textContent );  // 透過 bind(this) 來強制指定該 function scope 的 this  $ajax(&#39;[URL]&#39;, function(res) {    console.log(this.textContent, res);  }.bind(this));}, false);</code></pre><p>像上面這樣，在 function 後面加上 <code>.bind(this)</code> 就可以強制將 ( ) 內的物件帶入至 callback function 內，於是 callback function 裡的 <code>this</code> 就會強制被指定成先前在 <code>bind( )</code> 裡面的內容了。</p><p>這裡有另一個簡單的比較：</p><pre><code class="js">var obj = {  x: 123};var func = function () {  console.log(this.x);};func();            // undefinedfunc.bind(obj)();  // 123</code></pre><p>如果你從上篇就一路看到這裡的話，相信你一定知道為什麼 <code>func()</code> 的執行結果是 <code>undefined</code> 了。那麼加上了 <code>bind</code> 之後的 <code>func.bind(obj)()</code> 執行的結果，會替我們將 <code>func</code> 的 <code>this</code> 暫時指向我們所設定的 <code>obj</code>。於是， <code>console.log(this.x)</code> 的結果自然就是 <code>obj.x</code> 也就是 123 了。</p><p>這裡你可以想像成把某個 function 在執行的時候，「暫時」把它掛在某個物件下，以便透過 <code>this</code> 去取得該物件的 Context。</p><p>實務上除了 ajax 的 callback function 以外，另外像是 <code>setTimeout</code>、<code>setInterval</code> 這類的 function，也滿常見需要特別處理 <code>this</code> 的場景。</p><h2 id="箭頭函數與-this"><a href="#箭頭函數與-this" class="headerlink" title="箭頭函數與 this"></a>箭頭函數與 this</h2><p>值得一提的是，從 ES6 開始新增了一種叫做 「<a href="https://hacks.mozilla.org/2015/06/es6-in-depth-arrow-functions/" target="_blank" rel="noopener">箭頭函數表示式</a>」 (Arrow Function expression) 的函數表達式，而箭頭函數有兩個重要的特性：<strong>更短的函數寫法</strong>與 <strong>this 變數強制綁定</strong>。</p><p>像這樣，我們可以直接在 callback function 中取用 <code>this.textContent</code> ：</p><pre><code class="javascript">el.addEventListener(&quot;click&quot;, function(event) {  console.log( this.textContent );  // 箭頭函數隱含強制指定 this 至 callback function 中  $ajax(&#39;[URL]&#39;, res =&gt; {    console.log(this.textContent, res);  });}, false);</code></pre><p>但要注意的是，無論是使用 <code>&#39;use strict&#39;</code> 或是再加上 <code>.bind(xxx)</code> 都無法改變 <code>this</code> 的內容，也不能作為建構子 (constructor)來使用。</p><p>另外，也有一些場景適不適合用箭頭函數的，像是剛剛提到的事件綁定：</p><pre><code class="js">el.addEventListener(&quot;click&quot;, (event) =&gt; {  // 小心這裡的 this 變成了 「window」而不是 「el」！  console.log( this );}, false);</code></pre><p>或是像 VueJS 中的 <code>methods</code> 、 <code>computed</code> 會需要透過 <code>this</code> 來取得實體的情況等：</p><pre><code class="js">new Vue({  data: {    num: 0  },  methods: {    getNum_Wrong: () =&gt; {      // wrong!      return this.num;    },    getNum: function() {      // right!      return this.num;    }  }});</code></pre><p>箭頭函數方便歸方便，若是你的 function 內會有需要用到 <code>this</code> 的情況時，就需要特別小心你的 <code>this</code> 是不是在不知不覺中換了人來當。</p><h2 id="call-與-apply"><a href="#call-與-apply" class="headerlink" title=".call() 與 .apply()"></a>.call() 與 .apply()</h2><p>既然講到了強制指定 <code>this</code> 的方式，看完了 <code>bind()</code>、「箭頭函數」，接下來就不能不講到 <code>call()</code> 與 <code>apply()</code>。</p><p>假設今天有個 function 長這樣：</p><pre><code class="js">function func( ){  // do something}</code></pre><p>那麼通常我們會直接這樣來呼叫它：</p><pre><code class="js">func( );</code></pre><p>當然你也可以用 <code>.call()</code> 或是 <code>.apply()</code> 來呼叫它：</p><pre><code class="js">func.call( );func.apply( );</code></pre><p>你可能會覺得奇怪，看起來沒什麼不同對吧，還要多打幾個字豈不是自找麻煩。但如果遇上了需要帶參數的時候，就又顯得有些不同。</p><p>基本上 <code>.call()</code> 或是 <code>.apply()</code> 都是去執行這個 function ，並將這個 function 的 context 替換成第一個參數帶入的物件，換句話說，就是強制指定某個物件作為該 function 的 this。</p><p>而 <code>.call()</code> 與 <code>.apply()</code> 的作用完全一樣，差別只在傳入參數的方式有所不同：</p><pre><code class="js">function func( arg1, arg2, ... ){  // do something}func.call( context, arg1, arg2, ... );func.apply( context, [ arg1, arg2, ... ]);</code></pre><p><code>.call()</code> 傳入參數的方式是由「逗點」隔開，而 <code>.apply()</code> 則是傳入整個陣列作為參數。</p><pre><code class="javascript">  var person = {    name: &quot;Kuro&quot;,    hello: function(thing) {      console.log(this.name + &quot; says hello &quot; + thing);    }  }  var person2 = {    name: &quot;Jack&quot;  };  person.hello(&quot;world&quot;);                  // &quot;Kuro says hello world&quot;  person.hello.call(person, &quot;world&quot;);     // &quot;Kuro says hello world&quot;  person.hello.apply(person, [&quot;world&quot;]);  // &quot;Kuro says hello world&quot;  person.hello.call(person2, &quot;world&quot;);    // &quot;Jack says hello world&quot;  person.hello.apply(person2, [&quot;world&quot;]); // &quot;Jack says hello world&quot;</code></pre><p>可以看到，同樣是呼叫 <code>person.hello()</code> 但是這裡可以透過 <code>.call()</code> 或是 <code>.apply()</code> 去指定當下的 <code>this</code> 是誰，而差別只在傳入參數的方式有所不同。</p><h2 id="bind-call-apply-的差異"><a href="#bind-call-apply-的差異" class="headerlink" title="bind, call, apply 的差異"></a>bind, call, apply 的差異</h2><p><code>bind()</code> 讓這個 function 在呼叫前先綁定某個物件，使它不管怎麼被呼叫都能有固定的 <code>this</code>。尤其常用在像是 callback function 這種類型的場景，可以想像成是先綁定 <code>this</code>，然後讓 function 在需要時才被呼叫的類型。</p><p>而 <code>.call()</code> 與 <code>.apply()</code>則是使用在 context 較常變動的場景，依照呼叫時的需要帶入不同的物件作為該 function 的 <code>this</code>。在呼叫的當下就立即執行。</p><hr><p>在這次的篇幅中，我們簡單介紹了 <code>.bind()</code>、<code>.call()</code>、<code>.apply()</code> 以及「箭頭函數」是如何對 <code>this</code> 的操作，那麼在下一篇文章當中，我們再來繼續探討「<strong>this 與前後文本 (context) 綁定的基本原則</strong>」，提供各位判斷「<strong>如何決定 this 是誰的順序</strong>」，同時也透過例題與練習來讓各位驗證對於 JavaScript <code>this</code> 的了解。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;延續上個主題，上回我們提到了 &lt;code&gt;this&lt;/code&gt; 是誰，是取決於 function 被呼叫的方式，這次我們繼續來談談 &lt;code&gt;this&lt;/code&gt; 與 &lt;code&gt;function&lt;/code&gt; 的關係，以及 function 中的 &lt;code&gt;.call()&lt;/code&gt; 、 &lt;code&gt;.apply()&lt;/code&gt; 與 &lt;code&gt;.bind()&lt;/code&gt; 是如何對 &lt;code&gt;this&lt;/code&gt; 來進行操作的。&lt;/p&gt;
    
    </summary>
    
      <category term="名為 JavaScript 的黑魔法" scheme="https://kuro.tw/categories/%E5%90%8D%E7%82%BA-JavaScript-%E7%9A%84%E9%BB%91%E9%AD%94%E6%B3%95/"/>
    
    
      <category term="javascript" scheme="https://kuro.tw/tags/javascript/"/>
    
      <category term="重新認識 JavaScript" scheme="https://kuro.tw/tags/%E9%87%8D%E6%96%B0%E8%AA%8D%E8%AD%98-JavaScript/"/>
    
      <category term="this" scheme="https://kuro.tw/tags/this/"/>
    
  </entry>
  
  <entry>
    <title>What&#39;s THIS in JavaScript ? [上]</title>
    <link href="https://kuro.tw/posts/2017/10/12/What-is-THIS-in-JavaScript-%E4%B8%8A/"/>
    <id>https://kuro.tw/posts/2017/10/12/What-is-THIS-in-JavaScript-上/</id>
    <published>2017-10-12T11:12:33.000Z</published>
    <updated>2019-12-06T11:28:47.402Z</updated>
    
    <content type="html"><![CDATA[<p>這系列的主題其實是節錄自去年 (2016) 我在<a href="https://5xruby.tw/talks/JavaScript-this-01" target="_blank" rel="noopener">五倍紅寶石開設的課程</a>，講的是 「<strong>This</strong>」 在 JavaScript 這門程式語言裡所代表的各種面貌。 然而最近無論是社群還是課堂教學，發現仍有不少剛入門的朋友對 JavaScript 的 <code>This</code> 代表的意義不太熟悉，那麼我想整理出這幾篇文章也許可以釐清你對 <code>This</code> 的誤解，反正資料也都還在，不如就整理出來與大家分享順便做個紀錄。</p><a id="more"></a><p>系列文快速連結：</p><ul><li><a href="https://kuro.tw/posts/2017/10/12/What-is-THIS-in-JavaScript-%E4%B8%8A/">What&#39;s THIS in JavaScript ? [上]</a></li><li><a href="https://kuro.tw/posts/2017/10/17/What-s-THIS-in-JavaScript-%E4%B8%AD/">What&#39;s THIS in JavaScript ? [中]</a></li><li><a href="https://kuro.tw/posts/2017/10/20/What-is-THIS-in-JavaScript-%E4%B8%8B/">What&#39;s THIS in JavaScript ? [下]</a></li></ul><p><br></p><h2 id="What-39-s-this"><a href="#What-39-s-this" class="headerlink" title="What&#39;s this?"></a>What&#39;s this?</h2><p>也許你在其他物件導向的程式語言曾經看過 this，也知道它會指向某個建構子 (constructor) 所建立的物件。但在 JavaScript 裡，this 所代表的不僅僅是那個被建立的物件。</p><p>先來看看 <a href="http://www.ecma-international.org/publications/standards/Ecma-262.htm" target="_blank" rel="noopener">ECMAScript 標準規範</a> 對 this 的定義：</p><blockquote><p>「The this keyword evaluates to the value of the ThisBinding of the current execution context.」</p><blockquote><p>「this 這個關鍵字代表的值為目前執行環境的 ThisBinding。」</p></blockquote></blockquote><p>然後來看看 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this" target="_blank" rel="noopener">MDN</a> 對 this 的定義：</p><blockquote><p>「In most cases, the value of this is determined by how a function is called.」</p><blockquote><p>「在大多數的情況下，this 會因為 function 的呼叫方式而有所不同。」</p></blockquote></blockquote><p>好，如果上面兩行就看得懂的話那麼就不用再往下看了，恭喜你。</p><p>...... 我想應該不會，至少我光看這兩行還是不懂。 所以，this 到底是什麼？</p><h2 id="This-是什麼？"><a href="#This-是什麼？" class="headerlink" title="This 是什麼？"></a>This 是什麼？</h2><ul><li><code>this</code> 是 JavaScript 的一個關鍵字。</li><li><code>this</code> 是 function 執行時，自動生成的一個內部物件。</li><li>隨著 function 執行場合的不同，<code>this</code> 所指向的值，也會有所不同。</li><li>在大多數的情況下， <code>this</code> 代表的就是呼叫 function 的物件 (<strong>Owner Object of the function</strong>)。</li></ul><p><br>好，先來個例子吧，從大家最熟悉的物件講起：</p><pre><code class="js">var getGender = function(){  return people1.gender;};var people1 = {  gender: &#39;female&#39;,  getGender: getGender};var people2 = {  gender: &#39;male&#39;,  getGender: getGender};console.log( people1.getGender() );console.log( people2.getGender() );</code></pre><p>來，猜猜 console 後的結果是什麼？</p><p>.<br>.<br>.<br></p><p>猜你妹啊，都寫死了 <code>return people1.gender;</code> ，當然是 &#39;female&#39; 。</p><p>那麼，來換一個：</p><pre><code class="js">var getGender = function(){  return this.gender;};var people1 = {  gender: &#39;female&#39;,  getGender: getGender};var people2 = {  gender: &#39;male&#39;,  getGender: getGender};console.log( people1.getGender() );console.log( people2.getGender() );</code></pre><p>這個時候，你應該會得到 「female」 與 「male」 兩種結果。</p><p>所以回到前面講的重點，從這個例子可以看出，即便 people1 與 people2 的 getGender method 參照的都是同一個 getGender function，但由於呼叫的物件不同，所以執行的結果也會不同。</p><p>people1 的 this.gender 指的是 people1 的 gender 屬性 (&#39;female&#39;)，而 people2 的 this.gender 指的是 people2 的 gender 屬性 (&#39;male&#39;)。</p><p>雖然上面聽起來很像廢話，但現在我們知道了 <span style="color: #f00">this 會因執行的環境與上下文 (context) 的不同，而有不同的結果</span>。</p><h2 id="this-不等於-function"><a href="#this-不等於-function" class="headerlink" title="this 不等於 function"></a>this 不等於 function</h2><p>上面講過， this 代表的是 function 執行時所屬的物件。而在 JavaScript 這個語言內，除了基本型別外的一切都是「物件」，那麼當 function 本身就是物件時，又如何呢？</p><p>一樣，來看個簡單的範例：</p><pre><code class="js">var foo = function() {  this.count++;};foo.count = 0;for( var i = 0; i &lt; 5; i++ ) {  foo();}</code></pre><p>猜猜看，當這段程式碼執行後， <code>foo.count</code> 會是多少？</p><p>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br></p><p>答案是 <span style="color: #f00">0</span> 。 我知道你可能不能接受，來聽我解釋。</p><p>前面講過，this 代表的是什麼？ 「this 代表的是 function 執行時所屬的物件」對吧？</p><p>在上面這個範例當中， foo 是 function，也是「<strong><span style="color: #f00">全域變數</span></strong>」。 而全域變數在 JavaScript 當中，其實就是全域物件的屬性。 全域物件在瀏覽器是 <strong>window</strong>，在 node 內叫做 <strong>global</strong>。</p><p>換言之，在呼叫 <code>foo()</code> 的時候，實際上 foo 所屬的物件就是 <code>window</code>。</p><p>所以說，當 <code>foo()</code> 在 for 迴圈裡面跑得很開心的時候， <code>this.count++</code> 始終都是對 <code>windoo.count</code> 在做遞增的處理。</p><p>而 <code>windoo.count</code> 理論上一開始會是 <code>undefined</code> ，在做了五次的 <code>++</code> 之後，你會得到一個 NaN 的結果，而 <code>foo.count</code> 依然是個 0 。</p><p>記住，<strong>this 代表的是 function 執行時所屬的物件，而不是 function 本身</strong>。</p><p><br></p><p>再來一個範例：</p><pre><code class="javascript">var bar = function() {  console.log( this.a );};var foo = function() {  var a = 123;  this.bar();};foo();</code></pre><p>相信經過前一個例題後，聰明的你應該知道 <code>foo();</code> 的執行結果應該是 <code>undefined</code> 了！</p><p>在這個範例中， <code>foo()</code> 可以透過 <code>this.bar</code> 取得 <code>bar()</code> ，是因為 <code>this.bar</code> 實際上是指向 <code>window.bar</code>，而 <code>bar()</code> 的 <code>this.a</code> 並非是 <code>foo</code> 中的 <code>123</code>，而是指向 window 的 a，所以會得到 <code>undefined</code> 的結果。</p><p>再來看下個範例。</p><pre><code class="javascript">var foo = &#39;foo&#39;;var obj = {  foo: &#39;foo in Object&#39;};var sayFoo = function() {  console.log( this.foo );};obj.sayFoo = sayFoo;obj.sayFoo();   // ?sayFoo();       // ?</code></pre><p>如果你跟著這篇文章看到這裡，應該不難判斷 <code>obj.sayFoo()</code> 與 <code>sayFoo()</code> 執行後的結果分別是什麼，這裡我就保留解答。想知道結果的朋友丟到 console 裡跑跑看就知道結果囉。</p><h2 id="巢狀迴圈中的-this"><a href="#巢狀迴圈中的-this" class="headerlink" title="巢狀迴圈中的 this"></a>巢狀迴圈中的 this</h2><p>在上篇的最後，來講一下很多人容易踩中的誤區，看範例：</p><pre><code class="javascript">var obj = {  func1: function(){    console.log( this === obj );    var func2 = function(){      // 這裡的 this 跟上層不同！      console.log( this === obj );    };    func2();  }};obj.func1();</code></pre><p>在這個範例當中，會有兩次的 console。</p><p>首先可以看到在 <code>func1</code> 裡面的 <code>console.log( this === obj )</code> (第四行) 會回應 <code>true</code>。</p><p>這個應該沒有問題，在本篇文章的前面已經介紹過，當 function 是某個物件的 method 時，他的 this 會指向物件本身。但是，到了 <code>func2</code> 的時候，一樣的 <code>console.log( this === obj )</code> (第八行)，卻是回應 <code>false</code>。</p><p><br>這裡必須說明兩個重點：</p><ul><li>JavaScript 中，用來切分變數的最小作用範圍 (scope)，也就是我們說的有效範圍的單位，就是 function。</li><li>當沒有特定指明 this 的情況下，預設綁定 (Default Binding) <code>this</code> 為 「全域物件」，也就是 window。</li></ul><p><br>換言之，在 <code>func2</code> 裡頭的 this，若是沒有特別透過 <code>call()</code> 、 <code>apply()</code> 或是 <code>bind()</code> 來指定的話，那麼這裡的 <code>this</code> 就是 <code>window</code>。</p><p>但仍有例外，在 ES5 的嚴格模式下，會禁止 this 自動指定為全域物件，像這樣：</p><pre><code class="js">var obj = {  func1: function(){    &quot;use strict&quot;;    console.log( this === obj );    var func2 = function(){      // 宣告成嚴格模式後，這裡的 this 會變成 undefined。      console.log( this );    };    func2();  }};obj.func1();</code></pre><h2 id="所以我說那個-this-到底是什麼又不是什麼"><a href="#所以我說那個-this-到底是什麼又不是什麼" class="headerlink" title="所以我說那個 this 到底是什麼又不是什麼"></a>所以我說那個 this 到底是什麼又不是什麼</h2><p>看到這裡，你應該要有的基本觀念：</p><ul><li>this 不是 function 本身</li><li>this 也不是 function 的 scope</li><li>this 與 function 在何處被宣告完全無關，而是取決於 function 被呼叫的方式</li><li>this 指的是，當 function 執行時，這個 scope 的 owner</li><li>當 function 是某個物件的 method，this 指的就是上層物件</li><li>全域變數的上層物件就是 window</li></ul><p><br></p><p>在下篇文章當中，我們會來繼續深入介紹 <code>function</code> 與 <code>this</code> 的關係，也會談到 JavaScript 的 <code>call()</code> 、 <code>apply()</code> 與 <code>bind()</code> 的作用。以及在不同應用面，我們要如何來判斷目前的 this 是誰？ 又要如何強制指定 this 是誰。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;這系列的主題其實是節錄自去年 (2016) 我在&lt;a href=&quot;https://5xruby.tw/talks/JavaScript-this-01&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;五倍紅寶石開設的課程&lt;/a&gt;，講的是 「&lt;strong&gt;This&lt;/strong&gt;」 在 JavaScript 這門程式語言裡所代表的各種面貌。 然而最近無論是社群還是課堂教學，發現仍有不少剛入門的朋友對 JavaScript 的 &lt;code&gt;This&lt;/code&gt; 代表的意義不太熟悉，那麼我想整理出這幾篇文章也許可以釐清你對 &lt;code&gt;This&lt;/code&gt; 的誤解，反正資料也都還在，不如就整理出來與大家分享順便做個紀錄。&lt;/p&gt;
    
    </summary>
    
      <category term="名為 JavaScript 的黑魔法" scheme="https://kuro.tw/categories/%E5%90%8D%E7%82%BA-JavaScript-%E7%9A%84%E9%BB%91%E9%AD%94%E6%B3%95/"/>
    
    
      <category term="javascript" scheme="https://kuro.tw/tags/javascript/"/>
    
      <category term="重新認識 JavaScript" scheme="https://kuro.tw/tags/%E9%87%8D%E6%96%B0%E8%AA%8D%E8%AD%98-JavaScript/"/>
    
      <category term="this" scheme="https://kuro.tw/tags/this/"/>
    
  </entry>
  
  <entry>
    <title>VueJS 元件載入模板 (template) 的幾種方式</title>
    <link href="https://kuro.tw/posts/2017/09/21/VueJS-%E5%85%83%E4%BB%B6%E8%BC%89%E5%85%A5%E6%A8%A1%E6%9D%BF-template-%E7%9A%84%E5%B9%BE%E7%A8%AE%E6%96%B9%E5%BC%8F/"/>
    <id>https://kuro.tw/posts/2017/09/21/VueJS-元件載入模板-template-的幾種方式/</id>
    <published>2017-09-21T04:36:12.000Z</published>
    <updated>2019-12-06T11:28:30.225Z</updated>
    
    <content type="html"><![CDATA[<p><img src="/static/img/vue-template.png"></p><p>平常已經在使用 VueJS 開發專案的朋友，相信對 Vue Components 的用法已經不陌生，而 Component 有個相當棒的特性，就是將 HTML 封裝起來，掛載在網頁上的時候，只需要透過自定義的 tag 如 <code>&lt;components&gt;&lt;/components&gt;</code> 就可以掛載至網頁上。</p><a id="more"></a><hr><p>如果你是透過 CDN 載入 VueJS 做開發的朋友，相信最熟悉的方式應該是 in-HTML Templates 的方式：</p><pre><code class="html">&lt;h1&gt;{{ hello }}&lt;/h1&gt;&lt;script type=&quot;text/javascript&quot;&gt;var vm = new Vue({  data: {    hello: &#39;Hello, World!&#39;  }})&lt;/script&gt;</code></pre><hr><p>當我們開始切分子元件 (Child Components) 之後，通常會使用到 <code>template</code> 這個 option，然後裡面可以填入 HTML 的模板字串：</p><pre><code class="html">&lt;greeter name=&quot;World&quot;&gt;&lt;/greeter&gt;</code></pre><pre><code class="javascript">Vue.component(&#39;greeter&#39;, {  template: &#39;&lt;div&gt; Hello, {{ name }}!&lt;/div&gt;&#39;,  props: [&#39;name&#39;],});</code></pre><hr><p>然而，隨著專案規模的擴增，我們的 HTML 模板結構可能會變得越來越大，光是用 <code>template</code> 直接掛上 HTML 字串時，可能你的程式架構就會變得不是那麼好閱讀、管理，這時候，我們可以把整個 HTML 模板區塊透過 <code>&lt;script id=&quot;xxx&quot; type=&quot;text/x-template&quot;&gt; &lt;/script&gt;</code> 這樣的 <code>&lt;script&gt;</code> 標籤來封裝我們的 HTML 模板，這種方式通常被稱為「X-Templates」：</p><pre><code class="html">&lt;script type=&quot;text/x-template&quot; id=&quot;my-component&quot;&gt;  &lt;div class=&quot;component&quot;&gt;A custom component of Vue!&lt;/div&gt;&lt;/script&gt;</code></pre><p>然後在 template 的 option 可以直接帶入對應的 CSS Selector，像這樣：</p><pre><code class="html">Vue.component(&#39;my-component&#39;, {  template: &#39;#my-component&#39;});</code></pre><hr><p>再來，如果你有聽過 Vue Components 的編譯作用域 (Compilation Scope) 的話，你應該會知道在子元件中帶入的任何 tag 是沒有意義的：</p><pre><code class="html">&lt;child-component&gt;  {{ message }}&lt;/child-component&gt;</code></pre><p>像上面這樣，<code>&lt;child-component&gt;</code> 在經過編譯之後，會直接把 {{ message }} 忽略掉。</p><p>如果這種時候，你又非得要在 <code>&lt;child-component&gt;</code> 安插 HTML 模板不可時，你就可以透過 <code>inline-template</code> 這個屬性來幫忙：</p><pre><code class="html">&lt;my-component inline-template&gt;  &lt;div&gt;    &lt;p&gt;These are compiled as the component&#39;s own template.&lt;/p&gt;    &lt;p&gt;Not parent&#39;s transclusion content.&lt;/p&gt;  &lt;/div&gt;&lt;/my-component&gt;</code></pre><p>但要小心，加入了 <code>inline-template</code> 之後，不要跟 <code>&lt;slot&gt;</code> 的有效範圍搞混了，<code>inline-template</code> 的內容是由子元件提供，而 <code>&lt;slot&gt;</code> 的內容是由父層所提供。</p><hr><p>有寫過 AngularJS 1 的朋友可能會問，過去寫 AngularJS 的時候，directive 裡面有個選項叫做 <code>templateUrl</code>，可以讓我們將模板儲存至另外一個 HTML 檔案，再透過這個 HTML 載入，那麼在 Vue 裡面是否也有類似用法呢？</p><p>在講這個之前，我們先來介紹 Vue-loader。Vue-loader 最大的功能就是他可以將 Vue 的元件封裝成單獨的 .vue 檔案，這個檔案同時包含了三個部分：<code>&lt;template&gt;</code> 、 <code>&lt;script&gt;</code> 以及 <code>&lt;style&gt;</code>。如果你有用過 Vue CLI 建立專案 (webpack、webpack-simple) 的話，應該對封裝 .vue 檔案的步驟不陌生，這個功能就是 Vue-loader 在背後替我們處理的。</p><p>相信大家都知道，既然已經封裝成 .vue 檔案，那麼 HTML 字串模板的部分就可以通通往 <code>&lt;template&gt;</code> 裡面放就好。</p><p>回到主題。那麼，如果要透過外部 HTML 檔案載入的話，跟這個有什麼關係？可能很多人不知道，.vue 檔案的各區塊，其實是可以用 <code>src</code> 屬性來載入外部檔案的。像這樣，你就可以透過外部的靜態檔案來做為你元件的內容來源：</p><pre><code class="html">&lt;template src=&quot;./template.html&quot;&gt;&lt;/template&gt;&lt;script src=&quot;./script.js&quot;&gt;&lt;/script&gt;&lt;style src=&quot;./style.css&quot;&gt;&lt;/style&gt;</code></pre><p>這部分可以参照 Vue-loader 文件的說明： <a href="https://vue-loader.vuejs.org/en/start/spec.html#src-imports" target="_blank" rel="noopener">https://vue-loader.vuejs.org/en/start/spec.html#src-imports</a></p><p>另外，如果你用的是 laravel-elixir 以及 laravel mix.browserify 已經幫你包裝好的方法的話，更可以直接這樣寫：</p><pre><code class="javascript">module.export = {  template: require(&#39;./templates/template.html&#39;),  data: function(){      return {      text: &quot;Hello World!&quot;      };  }};</code></pre><p>template.html 像這樣：</p><pre><code class="html">&lt;h1&gt;{{ text }}&lt;h1&gt;</code></pre><hr><p>上面就是在 VueJS 開發專案上面常見的模版掛載方式，提供給大家參考。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;img src=&quot;/static/img/vue-template.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;平常已經在使用 VueJS 開發專案的朋友，相信對 Vue Components 的用法已經不陌生，
而 Component 有個相當棒的特性，就是將 HTML 封裝起來，掛載在網頁上的時候，只需要透過自定義的 tag 如 &lt;code&gt;&amp;lt;components&amp;gt;&amp;lt;/components&amp;gt;&lt;/code&gt; 就可以掛載至網頁上。&lt;/p&gt;
    
    </summary>
    
      <category term="vue.js" scheme="https://kuro.tw/categories/vue-js/"/>
    
    
      <category term="vue.js" scheme="https://kuro.tw/tags/vue-js/"/>
    
  </entry>
  
  <entry>
    <title>[JSDC] 全台最大 JS 研討會前直播上線啦！</title>
    <link href="https://kuro.tw/posts/2017/09/21/JSDC-%E5%85%A8%E5%8F%B0%E6%9C%80%E5%A4%A7-JS-%E7%A0%94%E8%A8%8E%E6%9C%83%E5%89%8D%E7%9B%B4%E6%92%AD%E4%B8%8A%E7%B7%9A%E5%95%A6%EF%BC%81/"/>
    <id>https://kuro.tw/posts/2017/09/21/JSDC-全台最大-JS-研討會前直播上線啦！/</id>
    <published>2017-09-20T17:57:48.000Z</published>
    <updated>2019-12-06T11:26:46.801Z</updated>
    
    <content type="html"><![CDATA[<p>先感謝 JSDC 團隊邀請，這次的直播企劃真的是超快閃，週一晚上接到邀請，週二花了一個小時喬 rundown，然後週三晚上就直接上了，幾乎是沒什麼準備的機會，超刺激。還好直播中沒有什麼太大的意外，也謝謝來自各方的觀眾願意來聽我這個大叔練肖威。</p><a id="more"></a><p>這次直播主要就是做個簡單的訪談，跟大家聊聊出社會這些年來是怎麼踏入前端領域的過程。<del>順便宣傳一下我今年在 JSDC 分享的議程主題這樣。</del></p><p>JSDC 報名傳送門： <a href="https://jsdc-tw.kktix.cc/events/jsdc2017" target="_blank" rel="noopener">https://jsdc-tw.kktix.cc/events/jsdc2017</a></p><p>可能中間有些胡言亂語的部分，就請大家多包涵。這裏就透過這篇文章簡單做個紀錄，也補充在直播沒提到的東西。</p><p><img src="/static/img/jsdc-live01.png"></p><h3 id="一整個鬧到不行的宣傳圖"><a href="#一整個鬧到不行的宣傳圖" class="headerlink" title="一整個鬧到不行的宣傳圖"></a>一整個鬧到不行的宣傳圖</h3><h3 id="那些在直播裡面「加油好嗎」的屁話我就不在這邊紀錄了-XD"><a href="#那些在直播裡面「加油好嗎」的屁話我就不在這邊紀錄了-XD" class="headerlink" title="那些在直播裡面「加油好嗎」的屁話我就不在這邊紀錄了 XD"></a>那些在直播裡面「加油好嗎」的屁話我就不在這邊紀錄了 XD</h3><p>直播實錄</p><iframe src="https://www.facebook.com/plugins/video.php?href=https%3A%2F%2Fwww.facebook.com%2FJSDC.TW%2Fvideos%2F1229118017193709%2F&show_text=0&width=560" width="560" height="315" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowtransparency="true" allowfullscreen="true"></iframe><hr><blockquote><p>什麼樣的契機讓你開始寫程式</p></blockquote><p>我其實從小學還在 Dos 時代就開始接觸電腦了，早期其實也不知道程式什麼的，都是為了打電動，頂多就是改改 autoexec.bat / config.sys 之類的設定檔，一直到五、六年級的 windows 95 時期才首次接觸網頁。 印象中那個時候很流行「烘培雞」 (HomePage) 什麼的，老師都喜歡教你 <code>&lt;h1&gt;</code> 就是標題， <code>&lt;center&gt;</code> 就是置中，還有一大堆的 <code>&lt;font&gt;</code> 標籤，以及從「網頁建置百寶箱」裡面的各種神秘代碼貼來的特效。</p><p>那個時期別說 CSS，連 JavaScript 是什麼都沒聽過。 XD</p><p>真正開始寫程式的時候，反而是高中時期參加電研社才首次接觸 C 語言、 VB 之類的。印象很深刻的是當時還用 Turbo C 寫了一個貪食蛇的小遊戲，現在都忘光了其實 XD</p><p>到了高二，雖然讀的是文組，仍因緣際會加入了某個神秘組織「<a href="https://www.ptt.cc/man/CKSHCNCA/M.1281545012.A.D0A.html" target="_blank" rel="noopener">成功高中校園網路策進會</a>」 (CKSHCNCA)，一直沒脫離電資領域。 那時幾乎天天中午都在玩 FreeBSD (打混的時候其實都在玩 BBS XD)，也是從那個時候開始接觸了 windows 以外的作業系統，來到另一個世界。</p><p>真正開始寫網頁系統，反而是在大學時期學 PHP、ASP.NET C# WebForm 等等，也靠寫網頁賺了一點錢，當然畢業後也就繼續相關的工作。</p><hr><blockquote><p>一直以來都是做前端工作的嗎</p></blockquote><p>在我剛畢業出來工作的時候，那時業界根本沒有像現在這樣分什麼前端後端的，工作對外都是戲稱「寫網頁的」。在成為專職的前端工程師以前，早期都是以 ASP、PHP 等後端語言為我工作上的主要技能。</p><p>第一份工作是網站開發，當年任職的是人數不多的小公司，沒有細分前後端的編制，所以所有東西都得一條龍自己硬上。那時候所謂的網頁開發，也就只是寫 HTML 標籤，CSS 還不太會，反正 table 排版無敵，要改顏色、字型就加 font 屬性...JavaScript ? 喔，那是拿來寫表單驗証、浮動廣告的，網路上範例抄一下頂多加個 alert 就很迷人了。</p><p>什麼 JavaScript 啊，CSS 的也是從出來工作之後才自己惡補，也差不多是從那時候才開始接觸 jQuery、Dojo 這樣的前端函式庫工具，距離現在應該也快十年了。</p><p>真正知道「前端工程」這個詞，其實是在 2010 年的事了。</p><p>時間來到了 2010 年的 OSDC 前夕。</p><p>得知 Douglas Crockford (JavaScript: The Good Parts 作者，JSON 的老爸) 受邀來台演講，透過關係知道他在 Yahoo 有內部分享，於是就很厚臉皮地當了一日訪客進去聽演講，順便參觀當時還在古亭的 Yahoo 奇摩。 坦白說，當天印象讓我最深刻的其實不是 Douglas 的演講內容。</p><p>除了整場分享以全英語交談，能聽懂的部分有限是原因之一以外，讓我最驚訝的是，原來在網頁開發中，前端工程的領域比我原先的認知還要來得複雜許多。而當時 Yahoo 奇摩甚至有數十位工程師專門負責前端的部分。 當下聽著他們的熱烈討論，除了顛覆我原本對網頁開發的錯誤觀念外，更引爆了我潛藏已久的前端魂，原來網頁前端技術是如此深奧且迷人，然後就此展開了我的大前端之路... XD</p><p>不愧人家說天下武功出少林，天下前端出雅虎。同時也差不多是從那開始才積極參加社群，技能點開始往前端的方向走去。</p><hr><blockquote><p>工作職涯方向</p></blockquote><p>工程師的職涯方向可以有很多不同的發展，不管是走管理的、往技術繼續鑽研的都可以是目標，甚至有技術背景的 PM 也都會是很搶手的選項。隨著工作經驗的成長，看技術的眼光也會有所不同。 從早期只要完成交辦工作為主要目標，到幾年後，你也許會經歷到需要在各種技術中做選擇，這時候你會有開始有權力可以挑選你想要的工具、技術了，但記得權力伴隨著責任，挑選技術的時候，你要考慮到產品與技術是否適合的場景、學習的成本、維護的成本等等。 甚至是產品的整體系統架構，你所選擇的技術棧 (technology stack) 是不是可以再優化，然後增加產品的轉換率等等的。</p><p>這些其實對工程師來說都是很棒的挑戰，也是未來可以思考的方向。</p><hr><blockquote><p>有沒有想要跟後輩說什麼，讓他們少走一些冤枉路</p></blockquote><p>看到這個問題，如果是年輕一輩的朋友，其實我認為沒有冤枉路，反而很贊成多繞點路，未來的選擇更多。很多人其實不知道自己適合什麼，也許他看到前端產業好像很夯，就一窩蜂想進入這個行業，但搞不好更適合後端也不一定。從另一個角度來看，有了後端經驗的前端工程師，比起設計轉職的前端，更能明白後端的困境，以及相同的溝通水準，不會有各說各話的感覺。</p><p>我認為保持開放的心，有時候多繞一下對人生職涯來說，也許還反而是件好事。</p><p>但如果有心往前端領域走的話，好好把 HTML / CSS / JavaScript 的基本功練好不會吃虧的。即使是 oo.js / xx.js 它的本質也還是 JavaScript 啊，把基本功練好，未來遇到新技術新工具的時候，至少你不會慌。</p><hr><blockquote><p>避免惰性的好方法</p></blockquote><h3 id="TDD-Talk-Driven-Development"><a href="#TDD-Talk-Driven-Development" class="headerlink" title="TDD: Talk-Driven Development"></a>TDD: Talk-Driven Development</h3><p>我認為人都有惰性，我也有 XD</p><p>今天當你剛學會一門新技術，想辦法練到可以去跟別人分享甚至協助解決他人的問題的程度時候，你就可以算是掌握了這門技術。最好的方式就是去投稿吧，去分享吧，屢試不爽。 而且有了時間的死線之後，你就會開始逼自己去整理，去內化這些資訊，好處多多。</p><p>而且不要怕講錯，講錯頂多被人糾正，但你卻因此得到了正確的資訊，學到東西就是你的。</p><hr><blockquote><p>對於畢業新鮮人/轉換跑道有沒有什麼建議</p></blockquote><p>履歷的累積最重要，剛畢業的時候你還能靠學歷，但過幾年之後根本不會有人在意你是哪個學校畢業的。去找個你覺得可以學到東西的地方，或是有機會碰到新技術的地方，然後好好累積工作經歷。</p><p>有個自我檢視的重點是，當你今天在這份工作無法為你的履歷多寫些什麼的時候，就是可以考慮轉職的時候了。當你繼續在某個地方工作，改變的只是工作的年份，而不是寫下你參與了某某專案，或是導入了什麼技術、為了公司完成了什麼任務的時候，再繼續待下去都是浪費人生。 因為這份工作的經歷無法為你的未來加分，只會繼續消耗人生，這很現實。</p><hr><blockquote><p>跟 JSDC 之間淵源</p></blockquote><p>一切都是為了「搶票」。 XD印象還很清楚，當年 2012 首屆 JSDC 票價還很便宜的時候，那時候的研討會幾乎是一開放報名就會秒殺的程度。主辦單位為了鼓勵大家投稿，就說「投稿者無論有無選上，都有優先購票權」</p><p>然後我就投稿了。然後我就上了。不僅不用搶票，連買票都可以省下來。</p><p>怎麼都想不到人生中的第一場上台分享就在中研院，2012 年。 緊張到靠北。</p><p>但也因此認識了很多社群前輩，以及 TDD (Talk-Driven Development) 學習法，可以說是利大於弊，哈。</p><hr><blockquote><p>為什麼想要講今年這個主題</p></blockquote><p>終於回到 JSDC 2017 主題本身了，經過上面的訪談其實可以知道，在這短短幾年內，前端技術領域的發展可以說是一日千里，而我最近持續在推廣的 VueJS 在這一兩年也有著持續地成長。</p><p>凡是存在必有它的意義。 從早期的 jQuery 到後來 Backbone、Angular JS (ng1)、React、 ng2 到我將為各位分享的 VueJS 等工具，其實可以發現某個語言、工具為什麼會受歡迎，很大的原因是它在那個時間點，解決了大多數人遇到的問題，或是在這個領域帶來了什麼新的概念，新的觀點，讓更多人得到啟發。</p><p>回到主題。 VueJS 從早期發展至今，也經歷多許多變革與更新。這次我在 JSDC 的分享，主要會由 VueJS 出發，除了針對 VueJS 新特性的介紹外，也簡單分析現代前端框架與前端開發生態圈的變化與演進。 :)</p><p><img src="/static/img/jsdc-live02.png"></p><p>最後再次感謝 JSDC 團隊邀請，讓我有機會擔任一日 <del>seafood</del> 網紅，感謝今天活動主持人 Ali，果然有正妹加持就是收視保證，也感謝只有聲音沒有人影的藏鏡人 Caesar 協助。</p><p>最後，也歡迎對 JavaScript 領域的朋友可以持續關注 JSDC 的各項活動，沒報名的朋友現在還來得及報名歐 XD</p><p>傳送門： <a href="https://jsdc-tw.kktix.cc/events/jsdc2017" target="_blank" rel="noopener">https://jsdc-tw.kktix.cc/events/jsdc2017</a></p><p>期待今年的 JSDC 可以與各位朋友、前輩有交流的機會囉，我們 JSDC 見 :)</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;先感謝 JSDC 團隊邀請，這次的直播企劃真的是超快閃，週一晚上接到邀請，週二花了一個小時喬 rundown，然後週三晚上就直接上了，幾乎是沒什麼準備的機會，超刺激。還好直播中沒有什麼太大的意外，也謝謝來自各方的觀眾願意來聽我這個大叔練肖威。&lt;/p&gt;
    
    </summary>
    
      <category term="JSDC" scheme="https://kuro.tw/categories/JSDC/"/>
    
    
      <category term="JSDC" scheme="https://kuro.tw/tags/JSDC/"/>
    
      <category term="前端" scheme="https://kuro.tw/tags/%E5%89%8D%E7%AB%AF/"/>
    
      <category term="直播" scheme="https://kuro.tw/tags/%E7%9B%B4%E6%92%AD/"/>
    
  </entry>
  
  <entry>
    <title>在 Vue 取得 jsonp 的方式</title>
    <link href="https://kuro.tw/posts/2017/08/03/%E5%9C%A8-Vue-Cli-%E5%8F%96%E5%BE%97-jsonp-%E7%9A%84%E6%96%B9%E5%BC%8F/"/>
    <id>https://kuro.tw/posts/2017/08/03/在-Vue-Cli-取得-jsonp-的方式/</id>
    <published>2017-08-03T06:04:48.000Z</published>
    <updated>2019-12-06T11:25:02.920Z</updated>
    
    <content type="html"><![CDATA[<p>如果你是從 V1 就開始用 Vue 開發的朋友，一定知道 Vue.js 重要的核心特性就是只關注於 view layout 的呈現與 Components 系統，提供最小化且必要的功能給開發者。 其他的功能都可以自由選用第三方套件來完成，這也是為什麼被稱為「漸進式框架」的原因。</p><p>所以，如果我們用 Vue.js 來開發網站，且想使用 ajax 從遠端取得資源的時候，也許有些人會選用 jQuery ($.get / $.ajax ...等) ，也有些人會用 vue-resource 來做搭配。 早期官方推薦 <a href="https://github.com/pagekit/vue-resource" target="_blank" rel="noopener">vue-resource</a>，到了 V2 官方的推薦 lib 改為 <a href="https://github.com/mzabriskie/axios" target="_blank" rel="noopener">Axios</a> 或是直接用<a href="https://developer.mozilla.org/zh-TW/docs/Web/API/Fetch_API" target="_blank" rel="noopener">原生 Fetch API</a>。</p><p>不過很可惜，除了<del>包山包海</del>的 jQuery 之外，其他目前都還不支援 jsonp 這樣的做法。 也就是說，如果你的專案上需要用到 jsonp ，而且又不希望掛上一大包的 jQuery，這裡有個簡單的套件可以幫助你。 (順便自己筆記)</p><a id="more"></a><hr><p>首先先安裝 jsonp 這個套件。</p><pre><code>$ npm install jsonp --save</code></pre><p>或</p><pre><code>$ yarn add jsonp</code></pre><p>然後在你的程式碼內這樣使用：</p><pre><code class="js">import jsonp from &#39;jsonp&#39;jsonp(&#39;http://www.example.com/foo&#39;, { /* DATA HERE */ },  function (err, data) {    if (err) {      console.error(err.message);    } else {      console.log(data);    }  });</code></pre><p>就可以了。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;如果你是從 V1 就開始用 Vue 開發的朋友，一定知道 Vue.js 重要的核心特性就是只關注於 view layout 的呈現與 Components 系統，提供最小化且必要的功能給開發者。 其他的功能都可以自由選用第三方套件來完成，這也是為什麼被稱為「漸進式框架」的原因。&lt;/p&gt;
&lt;p&gt;所以，如果我們用 Vue.js 來開發網站，且想使用 ajax 從遠端取得資源的時候，也許有些人會選用 jQuery ($.get / $.ajax ...等) ，也有些人會用 vue-resource 來做搭配。 早期官方推薦 &lt;a href=&quot;https://github.com/pagekit/vue-resource&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;vue-resource&lt;/a&gt;，到了 V2 官方的推薦 lib 改為 &lt;a href=&quot;https://github.com/mzabriskie/axios&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Axios&lt;/a&gt; 或是直接用&lt;a href=&quot;https://developer.mozilla.org/zh-TW/docs/Web/API/Fetch_API&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;原生 Fetch API&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;不過很可惜，除了&lt;del&gt;包山包海&lt;/del&gt;的 jQuery 之外，其他目前都還不支援 jsonp 這樣的做法。 也就是說，如果你的專案上需要用到 jsonp ，而且又不希望掛上一大包的 jQuery，這裡有個簡單的套件可以幫助你。 (順便自己筆記)&lt;/p&gt;
    
    </summary>
    
      <category term="vue.js" scheme="https://kuro.tw/categories/vue-js/"/>
    
    
      <category term="vue.js" scheme="https://kuro.tw/tags/vue-js/"/>
    
      <category term="jsonp" scheme="https://kuro.tw/tags/jsonp/"/>
    
  </entry>
  
</feed>
