Javascriptのクラスで足し算と引き算ができる計算機を作成しました【JavaScript/Class】

JavaScript

Javascriptのクラス構文の練習で色々な計算機を作成しました。

足し算できる計算機のサンプル

クラスの構文は次の通りです。
まずは足し算をする機能をもったクラスを作成します。

// クラス名、最初は大文字
class Sample {      
  // コンストラクタ
  constructor(num) {
    this.num = num;   // 引数を格納
  }
  // メソッド
  plus(val) {
    return this.num + val;
  }
}

// インスタンス化
let sample = new Sample(10);

// クラスの機能を使う
console.log(sample.num); // 10
console.log(sample.plus(20)); // 30

クラスの使い方(インスタンス化)

クラスを使用するときはインスタンス化します。
以下のように「new」をつけるとインスタンス化できます。インスタンス化したオブジェクトは変数に格納することもできます。

// インスタンス化 ※引数に10の値を設定
let sample = new Sample(10);

ドットでつなげることで、クラスで設定したプロパティやメソッドを使うことができます。

// クラスの機能を使う
console.log(sample.num); // 10
console.log(sample.plus(20)); // 30

引数の受け渡し方

インスタンス化した時の引数はconstructor関数で受け取れます。
constructor関数はインスタンス化した際に実行される関数です。※戻り値がない特殊な関数です。
以下のようにインスタンス化した時の引数「10」をコンストラクタ関数で受け取ることができます。
次の場合、「num」で引数を受け取っています。

// コンストラクタ
constructor(num) {
this.num = num; // 10
}

クラス内で使えるようにthisの変数に格納して使います。

足し算と引き算ができる計算機のサンプル

先ほどは足し算しかできませんでしたが、以下は引き算もできるサンプルです。計算結果が次の式に使われるようにしました。

See the Pen calculation-class by donguri2020 (@m-ke) on CodePen.

クラスとメソッドの引数を指定すると計算結果がHTMLで表示されるようになっています。

// オブジェクトの作成
const calculation = new Calculation(10);

document.querySelector(".result01").innerText = calculation.plus(20);
document.querySelector(".result02").innerText = calculation.minus(5);

クラスの部分を抜き出したものがこちらです。

// クラス
class Calculation {      
  // コンストラクタ
  constructor(val) {
    this.result = null;   
    this.val = val;
  }
  // 加算メソッド
  plus(val) {
    let num = this.result === null ? this.val : this.result;
    this.result = num + val;
    return (`${num} + ${val} = ${this.result}`);
  }

  // 減算メソッド
  minus(val) {
    let num = this.result === null ? this.val : this.result;
    this.result = num - val;
    return (`${num} - ${val} = ${this.result}`);

  }
}

ポイントとしては、constructor関数で計算結果を受け取る変数resltをnullで初期化していること。

this.result = null; 

計算結果をresultに格納することで、次の計算に使用できる点です。
※計算結果がない(null)の場合、コンストラクタ関数で引き渡された引数が使用されます。

let num = this.result === null ? this.val : this.result;

ビジュアル的に計算機っぽくしたサンプル

先程の計算機は値を直接変更しないと計算できませんでした。今度はブラウザから操作できる計算機を作ってみます。ビジュアルもCSSでそれっぽくします。

See the Pen calculation-class02 by donguri2020 (@m-ke) on CodePen.

計算式とその結果を表示させたかったので、先ほどと別物のコードになりました。
長くなったので、ポイントだけメモしておきます。

HTML

result要素に計算式と結果が表示されるようにします。
数字と+=Cにはそれぞれクラス名を設定しておきます。

<div class="calculation">
  <div class="result">0</div> // 計算式と結果を表示
  <div class="items">
    <div class="num">7</div>
    <div class="num">8</div>
    <div class="num">9</div>
    <div class="num">4</div>
    <div class="num">5</div>
    <div class="num">6</div>
    <div class="num">1</div>
    <div class="num">2</div>
    <div class="num">3</div>
    <div class="num">0</div>
    <div class="plus">+</div> // +ボタン
    <div class="minus">-</div> // ーボタン
    <div class="clear">c</div> // クリアボタン
    <div class="equal">=</div> // イコール(計算)ボタン
  </div>
</div>

CSS

.calculation {
  background: #545454;
  padding: 14px;
  max-width: 680px;
  margin: 0 auto;
}
.result {
  background: #fff;
  padding: 10px 30px;
  margin: 10px;
  border-radius: 20px;
  font-size: 2em;
  font-weight: bold;
  text-align: right;
  height: 1.2em;
}
.items {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
}
.items div {
  flex: 1 1 20%;
  margin: 10px;
  padding: 10px;
  background: #fff;
  border-radius: 20px;
  font-size: 1.2em;
  font-weight: bold;
  text-align: center;
  transition: all 0.5s 0s ease;
}
.items div:hover {
  background: #ccc;
  cursor: pointer;
}

.items div.plus,
.items div.minus,
.items div.clear {
  background: #ff9800;
  color: #fff;
  transition: all 0.5s 0s ease;
}
.items div.plus:hover,
.items div.minus:hover,
.items div.clear:hover {
  background: #fff;
  color: #ff9800;
}
.items div.equal {
  flex: 2 2 45%;
  background: #f44336;
  color: #fff;
  transition: all 0.5s 0s ease;
}
.items div.equal:hover {
  background: #fff;
  color: #f44336;
}

Javascript

計算機能があるクラスを作成する

先ほどと同じくクラス構文で記載します。
名前の先頭に「_」がついているプロパティやメソッドは、クラスの外で使って欲しくない要素であることを明示しています。今回は次のプロパティ、メソッドになります。

_resultTxtには計算式と計算結果を格納します。
_resultFlagは計算が完了したことを判定します。(初期値:false)
_clearCalculation() 計算が完了していたら_resultTxtの値をクリアします。

// クラス
class Calculation {
// コンストラクタ
constructor(val) { 
    this._resultTxt = "";  // 計算結果と式を格納
    this._resultFlag = false; // 計算が完了したかの判定
}
equal() {
    try {
        let formula = new Function('return ' + this._resultTxt); //文字列で計算
        this._resultTxt = formula().toString();
        this._resultFlag = true;
        return this._resultTxt;
    } catch (error) {
        console.error(error);
    }
}
changeTxt(val) {
    this._clearCalculation(); //計算結果がある場合、クリアする
    if(val == "+" || val == "-") { // 続けて+=が入力できないようにする
        if(this._resultTxt.slice(-1) == val) {
            val = "";
        }
    }
    return this._resultTxt += val;
}
clear() {
    this._resultTxt = "";
    return "";
}
_clearCalculation() {
    if(this._resultFlag) {
        this._resultTxt = "";
        this._resultFlag = false;
        }
    }
}

クリックイベントで取得した文字列を連結する

クリックした時に取得できる数字や+-の符号を、文字列として連結します。
連結した文字列は_resultTxtに格納し、関数の戻り値として返します。結果的にはHTMLのresult要素に戻った値が表示されます。

changeTxt(val) { // valはクリックした数字or符号の文字列が渡ってくる
    return this._resultTxt += val;
}

// 関数呼び出し元
// result.innerText = calculation.changeTxt(itemNum);  // itemNumはクリックしたタグの文字列

文字列を計算する

この計算機のポイントは、文字列の計算式を実行するところです。
そのため計算式は、new Function構文で実行します。この構文だと文字列を関数として実行できます。

new Function構文(例)

// a,bは引数
let sum = new Function('a', 'b', 'return a + b');

alert( sum(1, 2) ); // 3

▼参考サイト

"new Function" 構文

今回は引数が不要なので、計算式が格納されている_resultTxtの計算結果をreturnで返します。
最終的には計算結果を文字列に変換し、再度_resultTxtに代入します。

equal() {
  let formula = new Function('return ' + this._resultTxt); //文字列で計算
  this._resultTxt = formula().toString();
  this._resultFlag = true; // 計算が完了したのでtrueに変更
  return this._resultTxt;
}

続けて計算する時は値をクリアする

続けて計算しようとすると、計算結果の後に値が入力されてしまいます。

これだと動きが不自然なので、計算結果が表示されているときに値を入力すると、計算結果はクリアされるようにします。

_clearCalculation() {
    if(this._resultFlag) {
        this._resultTxt = "";
        this._resultFlag = false;
        }
    }

_resultFlagは、計算が完了した時にtrueになります。(初期値はfalse)。この値を判定に使用して計算結果をクリアします。

クラスをクリックイベントで使う

クラスを使うにはインスタンス化します。今回、ボタンをクリックしたら関数を実行したいので、クリックイベントにメソッドを割り当てます。

// インスタンス化
let calculation = new Calculation();

// 計算結果表示
const result = document.querySelector(".result");

document.querySelectorAll(".items div").forEach(item => {
    // 要素の値
    let itemNum = item.innerText;

    item.addEventListener('click', function() {
    // イコールをクリックしたら
    if(item.classList.contains('equal')) {
        result.innerText = calculation.equal();
    // クリアをクリックしたら
    } else if(item.classList.contains('clear')) {
        result.innerText = calculation.clear();
    // 数字をクリックしたら
    } else {
        result.innerText = calculation.changeTxt(itemNum);
    }
    })
})

ボタンの処理の切り分けは、特定のclassを持っているかで判定します。
以下は、「clear」のclassを持っている要素を判定しています。

item.classList.contains('clear')

キーボードでも操作できるようにする

最後にキーボードでも操作できるようにします。
実装は簡単で、クリックイベントをキーボードイベントに変更するだけです。
以下のコードを追加すると、キーボードでも操作できるようになります。

// キーボードイベント
document.addEventListener('keypress', keypress);
const regexNum = new RegExp('[0-9+-]');
const regexEnter = new RegExp('(=|Enter)');
function keypress(e) {
    // イコールキーが押されたら
    if(regexEnter.test(e.key)) {
        result.innerText = calculation.equal();
    // cキーが押されたら
    } else if(e.key == 'c') {
        result.innerText = calculation.clear();
    // 数字キーが押されたら
    } else if(regexNum.test(e.key)) {
        result.innerText = calculation.changeTxt(e.key);
    } else {
        return false; 
    }
}

押されたキーの値によって、どのメソッドを実行するか正規表現で判定します。

// e.keyは押されたキーの値
// regexEnterは正規表現(=|Enter) =かEnterを判定
// regexObj.test(str)は、正規表現と文字列の一致をtrueかfalseで返すメソッド
regexEnter.test(e.key)

まとめ

簡単な計算機の作成でしたが、クラス構文にすることで機能がまとまってスッキリしました。

今回は足し算と引き算だけの計算機ですが、掛け算や割り算もクラスにメソッドを追加すればすぐに作成できると思います。