2020年4月26日 星期日

[六角學院] [攻略心得] JavaScript 新手 JS 地下城3F - 計算機



[攻略前言]


在一開始看到這關寫著:
【自我學習】請在此關卡中「自學一個你原本不太會的技巧」,投稿時分享你透過哪些資源學習,並寫範例程式碼講解該技巧,以及你如何應用在此關卡上。

此刻我心裡OS:這就是逼我複習 Vue.js 啊XD



所以又乖乖的去看之前「JavaScript 入門篇 - 學徒的試煉」課程中 Vue.js 的 DLC,
這真的是很佛心,因為這是之前洧杰老師開的線上問答會所分享的內容,
內容完整的放在 Youtube 上,隨時都可以複習。

於是在攻略前我重新複習了以下2段影片,然後練習跟作業跟著再做一次。
Vue.js 教學 - 幼幼班入門篇 (上)
Vue.js 教學 - 幼幼班入門篇 (下)

原本以為複習完就可以很快地把計算機寫完,但事實證明我錯了XD

1. 計算機的「眉角」(台語:小細節、訣竅) 比想像中的多,
之前 Vue.js 教學影片中所示範的匯率計算機根本就是小巫見大巫orz
不過雖然計算機「眉角」很多,還是不推薦將所有細節規劃好才開始動工,
大方向有就先走,然後遇到 bug 再一個一個突破。
因為你會發現越寫才發現越來越多細節 (bug),
強求自己一開始就要全部到位是不可能的XD

哦對了,很多時候在迷惘計算機應該是怎麼運作的時候,
我會打開電腦的小算盤來試按看看~

2. 最初我想著計算機就是理所當然要能夠用鍵盤按啊,
所以開發方向完全朝著鍵盤輸入做,
好不容易差不多完成了,才發現一個很大的硬傷,
我不知道用 Vue.js 怎麼在 body 偵測 keydown 事件orz
到目前為止只能在 input 裡 key下數字,但這樣完全脫離小算盤的設定QQ
小算盤怎麼會一定要在某個範圍才能 key 數字...... 我自己心裡那關完全過不去。
試過很多方法都試不出來,看了很多相關文章,
我覺得應該是 Vue.js 我目前會的還不夠多,
於是捨棄之前的所有進度(也不到完全捨棄,大致上程式架構沒變),
還是乖乖朝滑鼠點擊開發。

雖然這樣,但前面的鍵盤輸入開發還是學到很多東西,沒有走冤枉路。
但也有走冤枉路的部份XD 但走冤枉路也是一種學習吧~
在攻略過程中額外學到的會在這篇的最後補充。




[正文]


因為計算機比我想像花更多時間完成,所以就不詳細寫過程了。
記錄過程中有卡關或分享攻略過程中好用的技巧,
讓之後的我或湊巧路過而跟我遇到一樣問題的人可以參考。

1. 寫 JavaScript 一定要用的套件:Lodash 

這個套件真的很好用!!!
它將很多 JavaScript 要花好幾行才能完成的功能包成一個功能就能使用。
(PS. 會知道這個套件,是因為有看 Vue.js 幼幼班起手式(下) ,裡面markdown範例有用到)
我這次有用到的像是:

_.isNaN(this.inputNumber):會看變數是否為NaN,是的話回傳true,不是則回傳false
在這關因為常用到 parseFloat,因此搭配 _.isNaN 使用,
判斷是否能正常轉成數字,有正常轉出來的才繼續往後運算。

延遲函數執行時間,這個在後面有獨立一點出來講~

其實在我剛從鍵盤輸入轉成滑鼠點擊的過渡期,
有用到一個很好用的函數,
雖然後來刪掉了,
但覺得好用還是分享一下。

在原生的 JavaScript 語法中,
可以透過 indexOf 去找陣列中的索引值。
operatorArray: ["+","-","×","÷"],
let operatorIndex = this.operatorArray.indexOf(inputCharacter);

但如果你的陣列不是單純的陣列,而是物件陣列呢?
而且你還想找尋特定屬性符合值的索引值呢?
Lodash 的 _.findIndex 一行就可以完成了~~~


operatorArray: [ 
            {keyCode:107,operator:"+"},
            {keyCode:109,operator:"-"},
            {keyCode:106,operator:"*"},
            {keyCode:111,operator:"/"},
],

let operatorIndex = _.findIndex(this.operatorArray, ["operator", event.target.textContent]);

_.findIndex(要搜尋的物件陣列, ["要搜尋的屬性",要搜尋的值]);

至於我怎麼會知道有這些函數可以用呢?
其實我是想做什麼就是到 Lodash 官網找,
官網上面其實就寫的很詳細了,
就找找看有沒有符合我需求的函數可以用這樣~

2. 善用 Vue.js 特性,直接在 html 上面寫 {{}} 看資料的變化 

有學過 Vue.js 的都知道,Vue.js 是資料導向的語言,
在 html 上寫 {{變數名稱}} 就可以直接讀取變數內容渲染在網頁上,
尤其在 debug 的時候超好用的~~~

像我這次就是這樣用,可以立即在 html 上看到現在運算符號跟輸入數字等變數內容,
在測試的時候,可以馬上看到操作所帶來的資料變化,
如果結果不如預期就可以看是哪個變數造成的,
debug 的時候真的很好用!


<div class="test">
            運算符號{{inputOperator}}
            算式{{inputFormula}}
            輸入的數字{{inputNumber}}
            結果{{result}}
            歷程記錄{{inputHistory}}
</div>
data: {
        inputFormula: "", // 所輸入的算式
        inputNumber: "",
        inputOperator: "", // 存現在所輸入的運算元
        inputHistory: "0", // 計算歷程記錄
        result: 0, // 實際運算結果
        resultComma: 0, // 運算結果加上千分位逗號
    },


不過有時候要 debug 可能不只要看 data 裡所定義的資料,
可能是 if else 邏輯寫錯造成錯誤,
所以不夠的話就埋一堆 console log 來 debug,
這部份就接下方第3點。

3. 0 == "" 居然會回傳 true

我有寫一個 if else 是要去判斷 inputHistory == "",
不為空的話要做後續事情,
但每次都發現結果不如預期,
於是就在 if else裡埋 console.log 看到底怎麼回事。
console.log("有走到這個邏輯嗎 this.inputHistory為空");
console.log(`this.inputHistory: ${this.inputHistory}`);
然後就赫然發現 inputHistory 值為0不為空的時候,
怎樣都會走進為空的邏輯,
覺得很奇怪,
我就去 chrome 的 console 輸入 0 == "",
才發現原來 0 == "" 會回傳 true orz

有時候有些洞沒遇過真的不知道,
多加善用 {{}} 跟 console.log 來 debug 吧~~~

4. 一開始 code 很亂不要緊,待寫完再好好整理 & review

其實我還沒整理 code 之前,code 亂到深處無怨尤orz
不過不要緊,等到功能面寫得差不多,debug 也告一段落後,
就可以開始整理 code 跟 review啦~
整理的過程中可以幫助自己順一次邏輯,
順完邏輯一定要記得順便寫註解,
幫助未來的自己也幫助他人~ 
(像我很多時候是被之前的自己寫的註解所救XD)
Review code 也可以順便發現自己之前寫的時候所犯的低級錯誤XD

例如 我居然在 if ( this.inputNumber == "" ) 裡面又寫了 this.inputNumber = ""
會走進這個邏輯裡面表示 this.inputNumber 一定為空啊......
不曉得當初為什麼會這樣寫XD 整理的過程就順便刪掉了XD

5. 過程中雖然有用到不錯的寫法,但不符合需求還是要忍痛改掉


原本計算機面板上要顯示 1.現在所輸入的數字 還是 2.計算結果,
因為這2個值同時只會存在一個,
採用 Vue.js 的 v-if v-else 來決定要顯示哪個值,如下:

<div class="inputResult">
 <p v-if="inputNumber != ''">{{inputNumber}}</p>
 <p v-else="inputNumber != ''">{{resultComma}}</p>
</div>

但因為 1.現在所輸入的數字 還是 2.計算結果 都要有千分位逗號的判斷顯示,
所以後來就用別的寫法了,但還是覺得 v-if 這個寫法很好。

6. 隨著計算機面板裡面文字長度,自動調整文字大小

在這關的要求還有其中一項是:
【特定技術】數字位數過多時,不能因此而破版,計算機功能皆須齊全

我原本一直想說應該用 css 就可以解決了吧,
超過容器大小會自己縮放比例,
但怎麼試跟找資料就是試不出來XD

所以後來是參考這個大大的做法→ 程式碼
根據輸入內容長度調整字型大小。


7. watch 速度比網頁渲染速度快,必須延遲處理

承上,乍看之下解決了,
不過發現在打完很長的數字,之後再打很短的數字,最後結果很長的數字的字型大小並沒有照自己所想的去縮放。

打完很長的數字 → 文字縮小
很短的數字 → 應該文字要放大,但文字還是一樣小
結果還是很長的數字 → 文字應該要縮小,但卻被放大了

試了很多方法,
例如提前函數呼叫的時間點,
但還是沒用,
堪稱本關最大魔王QQ
後來一樣用 console.log 來看看到底怎麼回事。


發現函數讀到的 p元素的寬度好像都會是上次的,
如上圖,p元素寬度已經被撐開到 336 px 了,
但函數讀到的卻是 20 px,
是上一次輸入的p元素寬度。(上次所輸入的數字只有1碼)

後來想想,
因為我是用 watch 去偵測 resultComma,一有變化就執行 autoResize(),
而 watch 執行速度太快,
當 watch 作用的時候讀到上一次 p元素的寬度。

那有沒有什麼方法可以延遲函數的執行,讓它可以在 p元素重新渲染才執行函數呢?
這時候想到「 Vue.js 幼幼班起手式(下) 」,
裡面 markdown 範例有用到 _.debounce 來延遲函數的執行,
於是就來用用看。


watch: {
 // 計算機面板依據情況可能顯示: 1.現在所輸入的數字 2.計算結果
 // 因此就要watch這2個值是否有改變,有改變就要確認結果的位數,看要不要加千分逗號
 result: "checkDigits",
 inputNumber: "checkDigits",
 resultComma: "autoResize", // 最後顯示的運算結果(含千分位逗號)如果有改變,就要確認寬度是否會超出面板,字體大小就要隨之調整
},

autoResize: _.debounce(function(e){ // 如果超出計算機面板,則要自動調整計算機字體大小
 // ***** watch 執行速度快於網頁泫染速度 (一watch到變數有異動就執行了)
 // 因此 watch 執行函數時,讀到的p寬度為未異動過的寬度,這樣會導致這個函數失效
 // 因此這邊使用 debounce 延遲函數執行

 const inputResult = document.querySelector(".inputResult p");
 let inputResultWidth = inputResult.scrollWidth; // 結果部份的寬度(寬度會隨著內容改變)
 let inputParentWidth = inputResult.parentElement.clientWidth; // 結果部份外面容器的寬度(固定)

 let scalePercent = inputParentWidth / inputResultWidth;
 this.fontSize *= scalePercent;
 if (this.fontSize > initialFontSize)  this.fontSize = initialFontSize;

 inputResult.style.fontSize = this.fontSize + "px";
},10),

成功了!!!!!!!!!!!!!!!!!!!
成功的瞬間真的超開心的~~~~~~~~~~~~ (灑花)





[額外補充]

1. 中文輸入法的雷:keyCode一律會變為229

如同前面提到的,
一開始我是朝鍵盤輸入的方式開發,
按下 Enter 鍵時,keyCode 為 13。
( 按鍵與 keyCode 對應關係可以參考這裡→ JavaScript Event KeyCodes )
但發現有時候無法正確對應 keyCode,
一樣埋 log 發現有問題的時候  keyCode 都為 229。

上網搜尋才發現原來切成中文輸入法時,keyCode 都會變為 229 orz


中文輸入在瀏覽器的鍵盤事件行為上有些特殊,但從未深究,這回算是比較清楚研究其中的差別。其實最明顯的差異是: 切到中文輸入法後,輸入文字將不會觸發KeyPress事件,只會有KeyDown,而且傳回的e.keyCode會一律是229。

2. Pug (感覺很常搭配 Vue.js 使用)

這次在找 Vue.js 的寫法時,
發現很多大大的 code 在 html 都會搭配 Pug 的寫法,
這真的很簡便耶!(有種 Python 的感覺XD)

參考:PUG Template

PUG to HTML: https://pughtml.com/

所以當看到有大大用 Pug 寫法時,我就拿去 to html 轉一下XD

以下也拿我這次寫的 html 轉成 Pug 看看,超簡潔XD

html
  head
  body
    #calculator.panel
      .inputBlock
        .inputHisory
          p {{inputHistory}}{{inputOperator}}
        .inputResult
          p {{resultComma}}
      ul.keyboard(@click.prevent='preProcess')
        li
          a(href='#') 7
        li
          a(href='#') 8
        li
          a(href='#') 9
        li.operator
          a(href='#') ÷
        li
          a(href='#') 4
        li
          a(href='#') 5
        li
          a(href='#') 6
        li.operator
          a(href='#') ×
        li
          a(href='#') 1
        li
          a(href='#') 2
        li
          a(href='#') 3
        li.operator(value='+')
          a(href='#') +
        li
          a(href='#') 0
        li
          a(href='#') 00
        li
          a(href='#') .
        li.operator
          a(href='#') -
        li.clear
          a(href='#') AC
        li.clear
          a(href='#') ⌫
        li.equal
          a(href='#') =
    script(src='https://cdn.jsdelivr.net/npm/vue@2.6.11')
    script(src='https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js')
    script(src='js/calculator_v8.js')




[攻略後心得]


覺得攻略這關後,有種升級的感覺XD
像這樣↓

中間歷經要從鍵盤輸入改寫滑鼠點擊,
也一度覺得越寫越亂,怎麼寫都會遇到 bug,
還好順利一個一個打敗路上遇到的怪,
然後終於熬到升級了QQ

3F攻略完,暫時休息一下再繼續去4F玩XD




[程式碼]


沒有留言:

張貼留言