【初学者・中級者向け】JavaScriptで電卓を作ろう!(eval使わずに)【JavaScript練習帳】

イヌ
イヌ

JavaScript練習帳の第一弾!
今回はWeb電卓だよ!

ウサギ
ウサギ

わー!わー!
JavaScript練習帳!

「JavaScriptのWebアプリを作って練習しよう!」をコンセプトに「JavaScript練習帳」をやっていきたいと思います!

第一弾は「Web電卓」です!

JavaScriptで電卓を作ろう!

注意!evalは取り扱い注意です!

JavaScriptで電卓を作ろうとすると、evalFunctionを利用した方法が多く見られます。

基本的はevalFunctionはXSSインジェクションの標的になるので、利用はおすすめできません。

参考 第7回 DOM-based XSS その2 - JavaScriptセキュリティの基礎知識gihyo.jp

もしどうしても利用しなければ行けないケースがあり eval を利用するのであれば、エスケープ処理や外部入力がないようにするなど、安全性には十分に気をつけて、実装することをおすすめします。

Web電卓

では早速今回実装したWeb電卓です!

ソースコード全体

HTML

今回は jsfiddle というオンラインエディタサービスを利用しています。

jsfiddleは自動的にCSSとJavaScriptを読み込んでくれる機能があるため、<link ref="stylesheet" href="./style.css"><script src="./app.js"></script>がHTMLに書かれていないのはそのためです。

<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Web電卓</title> </head> <body> <h1>Web電卓</h1> <div class="container"> <input type="text" class="display" id="display" value="0" disabled> <div class="row first"> <button type="button" class="btn" id="clear">C</button> <button type="button" class="btn js-operator" value="/">÷</button> </div> <div class="row"> <button type="button" class="btn js-number" value="7">7</button> <button type="button" class="btn js-number" value="8">8</button> <button type="button" class="btn js-number" value="9">9</button> <button type="button" class="btn js-operator" value="*">×</button> </div> <div class="row"> <button type="button" class="btn js-number" value="4">4</button> <button type="button" class="btn js-number" value="5">5</button> <button type="button" class="btn js-number" value="6">6</button> <button type="button" class="btn js-operator" value="-">-</button> </div> <div class="row"> <button type="button" class="btn js-number" value="1">1</button> <button type="button" class="btn js-number" value="2">2</button> <button type="button" class="btn js-number" value="3">3</button> <button type="button" class="btn js-operator" value="+" >+</button> </div> <div class="row last"> <button type="button" class="btn two-columns js-number" value="0">0</button> <button type="button" class="btn js-number" value=".">.</button> <button type="button" class="btn" id="result" value="=">=</button> </div> </div> </body>
Code language: HTML, XML (xml)

CSS

.container { width: 216px; } .container .display { display: inline-block; margin-bottom: 8px; width: 208px; font-size: 28px; text-align: right; } .container .row { margin-bottom: 4px; } .container .row.first { display: flex; justify-content: space-between; } .container .row.last { margin-bottom: 0; } .container .row .btn { width: 50px; height: 50px; border-radius: 50%; font-size: 28px; } .container .row .btn.two-columns { width: 105px; border-radius: 30px; text-align: left; padding-left: 15px; }
Code language: CSS (css)

JavaScript(JSと略すことが多いです)

// ターゲットのElementを単体で取得 const displayElement = document.querySelector('#display'); const resultElement = document.querySelector('#result'); const clearElement = document.querySelector('#clear'); // ターゲットのElementを複数で取得 const numberElements = document.querySelectorAll('.js-number'); const operatorElements = document.querySelectorAll('.js-operator'); // 各変数を定義する let isInsertNumber = true; // 数字入力中かどうか let isResult = false; // =を押した後かどうか let currentNumber = '0'; // 現在選択中の数字 let currentOperator = ''; // 現在選択中の演算子 let numbers = []; // 押した数字の配列を格納する let operators = []; // 押した演算子の配列を格納する // 計算窓に表示する const showDisplay = () =&gt; { if (isInsertNumber) { const text = String(currentNumber); const newText = text.replace(/^0{1,}([^.])/, '$1'); displayElement.value = newText; } else { displayElement.value = total(); } }; // 演算子の計算する const calculate = (prev, current, index) =&gt; { switch (operators[index]) { case '+': return prev + current; case '-': return prev - current; case '*': return prev * current; case '/': return prev / current; default: console.log(`${operator} is not working...`); } }; // 数字配列の左端から順に一つ前の演算子で計算して、すべての計算結果を返す const total = () =&gt; { return numbers.reduce((prev, current, index) =&gt; { return calculate(prev, current, index - 1); }); }; // 数字をクリックしたときの振る舞い ... (1) const selectNumber = num =&gt; { // = を押した直後 if (isResult) { currentNumber = '0'; isResult = false; } // 演算子を押した直後 if (!isInsertNumber) { operators.push(currentOperator); currentNumber = '0'; isInsertNumber = true; } // 連続の . は不可 if (num === '.' &amp;&amp; currentNumber.includes('.')) { return; } currentNumber += num; }; // 数字ボタンの一つ一つに (1) の振る舞いをセットしていく numberElements.forEach(numberElement =&gt; { numberElement.addEventListener('click', event =&gt; { selectNumber(event.target.value); showDisplay(); }); }); // 演算子をクリックしたときの振る舞い ... (2) const selectOparator = op =&gt; { if (isInsertNumber) { numbers.push(Number(currentNumber)); isInsertNumber = false; } currentOperator = op; }; // 演算子ボタンの一つ一つに (2) の振る舞いをセットしていく operatorElements.forEach(operatorElement =&gt; { operatorElement.addEventListener('click', event =&gt; { selectOparator(event.target.value); showDisplay(); }); }); // = をクリックしたときの振る舞い ... (3) const showResult = () =&gt; { if (isInsertNumber &amp;&amp; currentOperator &amp;&amp; !isResult) { numbers.push(Number(currentNumber)); currentOperator = ''; currentNumber = total(); numbers = []; operators = []; isResult = true; } }; // = ボタンに (3) の振る舞いをセットする resultElement.addEventListener('click', () =&gt; { showResult(); showDisplay(); }); // C をクリックしたときの振る舞い ... (4) const clear = () =&gt; { numbers = []; operators = []; currentNumber = 0; currentOperator = ''; isInsertNumber = true; }; // C ボタンに (4) の振る舞いをセットする clearElement.addEventListener('click', () =&gt; { clear(); showDisplay(); });
Code language: JavaScript (javascript)

ソースコード解説

JavaScript概要

JavaScriptに関しては、ES2015以降の機能を利用しています。(Mac + Google Chromeのみで動作確認しています。)

ES2015というのは、JavaScriptの新文法のことで、ES2015より以前のバージョンは、ES5やES4などという呼び方をします。

例えば、変数宣言は

varlet, const
Code language: JavaScript (javascript)

関数式は

var sample = function() { }; ↓ const sample = () => { };
Code language: JavaScript (javascript)

などの構文が利用できるようになりました。

ポイント説明

セレクタについて

HTMLに設定しているセレクタで、

  • 単体で取得する要素に関してはIDを付与
  • 複数で取得する要素にはjs-という接頭辞(プレフィックスといったりします)を付与

しています。

IDは1ページで1つしか利用できないため、複数の対象を取得したい場合はclassセレクタを利用することなります。

(実務的にはidを一切利用しないこともあるので、単体であってもclassセレクタのみで取得する現場もあると思います)

js-プレフィクス

なぜ、クラスセレクタにjs-のプレフィックスを付けたかというと、

CSSは見た目を調整する役割だからです。JSで取得するときに利用するセレクタが主な役割ではないので、その要素に付与しているclassセレクタが、CSSのために付与されているのか。JSのために付与されているか。をすぐに判定するための目印として、付けています。

こちらも現場の運用によりますが、対象が膨大になるとひと目でみて、「あ、js-が付いてるからJavaScriptで利用してるんだな」と理解できるので、事前に何らかのルール付けしておくと幸せになれると思います。

計算ロジック

evalは利用せず、配列に保管している数字データ、演算子データをreduceで計算することによって、結果を導き出しています。

reduceが関数としては難しいのですが、ループの中で、データがどんどん計算されていって、煮詰められた結果が返される感じです。

。。。

なんのこっちゃですね。以下を見てください。

const array = [1, 2, 3, 4]; const reducer = (accumulator, currentValue) => { return accumulator + currentValue; }; const result = array.reduce(reducer); console.log('result'); // 10
Code language: JavaScript (javascript)

配列が呼び出しているreduceというメソッドは、配列の個数分実行されることになります。

accumulator と currentValue が加算されるコールバックが渡っているので、毎回足した結果が、accumulatorから取得できる感じですね。

初期値設定がないため、

  • 1回目は1のまま
  • 2回目は1回目の1と配列2つ目の2が足されて3
  • 3回目は2回目の3と配列3つ目の3が足されて6
  • 4回目は3回目の6と配列4つ目の4が足されて10

というイメージです。

今回の計算ロジックではこのreduceを利用しています

// 数字配列の左端から順に一つ前の演算子で計算して、すべての計算結果を返す const total = () => { return numbers.reduce((prev, current, index) => { return calculate(prev, current, index - 1); }); };
Code language: JavaScript (javascript)

押された数字の数(numbers)分、reduceを実行して、calculateの計算結果を返しています。

参考サイト

今回の計算ロジックは、以下のサイトを参考にさせていただきました。ありがとうございます!

参考 Vue.js で 計算電卓アプリを作るメモブログ
イヌ
イヌ

不明なことがあれば、下にあるボタンから、Twitterでお気軽に質問してね!

ウサギ
ウサギ

具体的にコードの書ける記事、どんどん増やしていこう!

またね〜

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です