JS 地下城挑戰 - 3F 計算機

圖片來源: 六角學院

前言

這篇文章將會把重點放在兩個部分:

  • eval() 函式
  • 正規式 (Regular Expression)

在寫這個題目以前,我沒有實際使用過正規式,這個概念大概是一個月以前才接觸的,最後雖然是有把功能寫出來,但是品質還有待加強,不過有小小進步這個挑戰很值得了,日後找機會再加強相關知識吧!

Demo
Github
Codepen

關卡資訊

UI 設計稿

  • 【特定技術】數字位數過多時,不能因此而破版,計算機功能皆須齊全
  • 【自我學習】請在此關卡中「自學一個你原本不太會的技巧」,投稿時分享你透過哪些資源學習,並寫範例程式碼講解該技巧,以及你如何應用在此關卡上。

eval()

語法

eval(string)

實例

可帶入一組字串,並執行字串中的 JavaScript 程式碼,且程式碼內可以包含變數及已存在物件的屬性。

1
2
3
4
5
6
7
8
9
10
11
12
eval('2 + 2')  // 4

//----------------------------
let x = 10
eval('x + 20') // 30

//----------------------------
eval('x=10; y=20; console.log(x * y)') // 200

//----------------------------
let obj = { name: 'Dylan', age: 18 }
eval('console.log(obj.name)'); // Dylan

如果 eval() 的參數不是字串,它將會將參數原封不動的返回該參數。

1
2
eval(new String("2 + 2")); // String {"2 + 2"}
eval("2 + 2"); // 4

如果要避免這個情況你可以先將參數進行轉字串的處理

1
2
var expression = new String("2 + 2");
eval(expression.toString()); // 4

本次關卡應用

在了解 eval() 的用法後,可以開始思考開如何運用在計算機這個題目中?

數字及運算符號功能

我們可以先宣告一組字串,代表計算機等待被計算的算式。

1
let formula = ''

當我們按下按鍵後,應該要為 formula 字串增加該按鈕的值。為了達到這點,所有的數字按鍵和加減乘除都需要新增事件監聽。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div class="button">0</div>
<div class="button">1</div>
<div class="button">2</div>
<div class="button">3</div>
<div class="button">4</div>
<div class="button">5</div>
<div class="button">6</div>
<div class="button">7</div>
<div class="button">8</div>
<div class="button">9</div>
<div class="button">.</div>
<div class="button">÷</div>
<div class="button">×</div>
<div class="button">+</div>
<div class="button">-</div>
1
2
3
4
5
document.querySelectorAll('.button').forEach((item) => {
item.addEventListener('click', function(event){
formula += event.target.innerText
}, false)
})

可以開啟這個 domo 操作一下,試著打一些簡單的加減乘除計算,再到 console 觀察 formula 變數的改變。到這個階段還有一個問題是可能會有一些不合規格的算式出現,如 1+÷1 這種無法被計算的情況,這就需要留到後面用正規式處理了。

接著繼續新增剩下的按鈕功能

= 功能

在使用者按下等於按鈕時將 formula 變數帶入 eval() 函數中,並將回傳值帶入 result 變數,也就是之後會渲染在計算機上的答案。

1
<div class="equal">=</div>
1
2
3
4
5
let result;

document.querySelector('.equal').addEventListener('click', function(){
result = eval(formula);
}, false)

AC 功能

清空功能最單純,按下後將 formula 變數改為空字串即可。

1
<div class="ac">AC</div>
1
2
3
4
document.querySelector('.ac').addEventListener('click', function(){
formula = '';
result = 0;
}, false)

⌫ 功能

點擊回上一步後,將 formula 字串的最後一個字元刪除。
做法是先用 split('')formula 字串轉為陣列,舉例說如果 formula 當前值為 1+1,經過 split('') 處理會變成陣列['1', '+', '1'],再用 pop() 將陣列最後一元素移除,然後再透過 join('') 轉回字串,結果 formula1+1 變成了 1+

參考: JavaScript 陣列處理常用方法

1
<div class="del"></div>
1
2
3
4
5
document.querySelector('.del').addEventListener('click', function(){
let toArr = formula.split('');
toArr.pop();
formula = toArr.join('');
}, false)

到這裡所有按鈕的功能告一段落,可以用這個 Demo 操作看看。接著我們來講講如何用正規式來篩選掉不合計算機規則的算式。

使用正規式過濾掉不合法的算式排列

如果對於正規式還不熟的朋友可以參考初探正規式這篇。

正規式是用來做什麼的呢?

正規式也可稱正則表達式、正規表達式…etc,我自己對於它的第一個印象是,這..是機器語言吧? 不覺得它是我能理解的東西,不過我們需要拋開偏見和恐懼,可以試著先去接觸看看,你會發現它是一個很棒的工具。

舉個正規式常見的應用,當我們在註冊某些網站的帳號時,為什麼當我們再輸入 Email 時,這個網站都會提醒我的 Email 格式是正確或是錯誤的,它是怎麼判斷的呢?其實這就是利用正規式來做檢測的,請參考[Javascript] Regular Expression – Email 表單驗證

替換乘除符號

剛剛我們在實作時有一個小問題,當我們點擊 ÷× 時,會直接將這兩個字元帶入 formula 變數中,若變數值為 6÷2,按下 = 按鈕後,變數被帶入 eval() 函式處理,結果拋出錯誤 "SyntaxError: Invalid or unexpected toke,原因是 eval() 函式是不認得 ×÷ 符號的,我們需要處理一下當使用者按下 ×÷ 時需要把它替換成 */,試著用正規式來做吧。

1
formula.replace(/\÷/g, `/`).replace(/\×/g, `*`);

異常數字處理

我們還需要排除開頭一個零以上和開頭零後面接數字的狀況,舉例說像式 00123 + 01235 的算式是不合理的,必須要做處理。

1
formula.replace(/^0[0-9]+/, `0`).replace(/([/*+-])0\d+/g, `$10`);

異常運算符號排列處理

正常的算式中不會出現相連的運算符號,比如 1+-2,或是運算符號出現在開頭的情況 + 123 + 321

1
formula.replace(/^[/*+-]+/, ``).replace(/([/*+-])[/*+-]+/g, `$1`);

異常小數點處理

接著是比較複雜一點的小數點處理

  • 算式開頭不能有小數點 .123 + 123
  • 不能有相連的小數點 1..23 + 8
  • 不會有 00.123 這樣的情況
  • 運算符號後面不能出現小數點 123 + .123
  • 同一組數字不會出現第二個小數點 123.123.3
1
2
3
4
5
6
7
function fn(formula) {
const newStr = formula.replace(/^\.*/, ``);
const _newStr = newStr.replace(/\.+/g, `.`);
const __newStr = _newStr.replace(/\D00\.(\d+)/g, `0.$1`);
const ___newStr = __newStr.replace(/([/*+-])\./g, `$1`);
return ___newStr.replace(/(\d+)\.+(\d+)\.*/g, `$1.$2`);
}

千分位

最後需要套用千分位,把運算結果渲染到計算機上,如果數字是 10000 會被轉換成 10,000

1
2
3
4
5
function toCurrency(formula) {
let newStr = formula.toString().split(".");
newStr[0] = newStr[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return newStr.join(".");
}

到這邊計算機的功能差不多完成了,可以到最後的 Demo 試著操作看看,再來就剩下一些畫面和細節的處理,有興趣的話請參考完整版