簡易的JS計算機實現代碼,js計算機代碼

來源:互聯網
上載者:User

簡易的JS計算機實現代碼,js計算機代碼

看看手機中的計算機,分為普通計算機和科學計算機

 自認腦袋不夠大,就實現一個普通版本的吧(支援正負數加減乘除等基本連續的運算,未提供括弧功能)

看看圖示效果:

一、知識準備

1+1 = ?

正常來說,我們看到這個運算式都知道怎麼運算,知道運算結果

但電腦不一樣,電腦無法識別出這串運算式,它只能識別特定的規則:首碼運算式+ 1 1 或尾碼運算式1 1 +

舉個栗子

(3 + 4) × 5 - 6 就是中綴運算式
- × + 3 4 5 6 首碼運算式
3 4 + 5 × 6 - 尾碼運算式 

所以為了實現程式的自動運算,我們需要將輸入的資料轉化為首碼或尾碼運算式

首碼、中綴、尾碼運算式的概念以及相互轉換方法在這裡就不多說了,這篇博文 說得比較清楚了

所以,在這個計算機的實現中,採用了尾碼運算式的實現方式,參考以上文章,重點關注這兩個演算法:

與轉換為首碼運算式相似,遵循以下步驟:
(1) 初始化兩個棧:運算子棧S1和儲存中間結果的棧S2;
(2) 從左至右掃描中綴運算式;
(3) 遇到運算元時,將其壓入S2;
(4) 遇到運算子時,比較其與S1棧頂運算子的優先順序:
(4-1) 如果S1為空白,或棧頂運算子為左括弧“(”,則直接將此運算子入棧;
(4-2) 否則,若優先順序比棧頂運算子的高,也將運算子壓入S1(注意轉換為首碼運算式時是優先順序較高或相同,而這裡則不包括相同的情況);
(4-3) 否則,將S1棧頂的運算子彈出並壓入到S2中,再次轉到(4-1)與S1中新的棧頂運算子相比較;
(5) 遇到括弧時:
(5-1) 如果是左括弧“(”,則直接壓入S1;
(5-2) 如果是右括弧“)”,則依次彈出S1棧頂的運算子,並壓入S2,直到遇到左括弧為止,此時將這一對括弧丟棄;
(6) 重複步驟(2)至(5),直到運算式的最右邊;
(7) 將S1中剩餘的運算子依次彈出並壓入S2;
(8) 依次彈出S2中的元素並輸出,結果的逆序即為中綴運算式對應的尾碼運算式(轉換為首碼運算式時不用逆序)。

與首碼運算式類似,只是順序是從左至右:
從左至右掃描運算式,遇到數字時,將數字壓入堆棧,遇到運算子時,彈出棧頂的兩個數,用運算子對它們做相應的計算(次頂元素 op 棧頂元素),並將結果入棧;重複上述過程直到運算式最右端,最後運算得出的值即為運算式的結果。
例如尾碼運算式“3 4 + 5 × 6 -”:
(1) 從左至右掃描,將3和4壓入堆棧;
(2) 遇到+運算子,因此彈出4和3(4為棧頂元素,3為次頂元素,注意與首碼運算式做比較),計算出3+4的值,得7,再將7入棧;
(3) 將5入棧;
(4) 接下來是×運算子,因此彈出5和7,計算出7×5=35,將35入棧;
(5) 將6入棧;
(6) 最後是-運算子,計算出35-6的值,即29,由此得出最終結果。

二、實現過程

第一步當然是搭建計算機的頁面結構,不是科學計算機,只提供了基本的運算功能,但也能即時地進行運算,顯示出完整的中綴運算式,運算後儲存上一條運算記錄。

要先說一下:本來想實現小數點功能的,但小數點的存在讓資料存放區與資料顯示的實現有了壓力,實現過程實在腦大,索性先取消這個功能。 

1. 頁面結構:

  <h5>計算計算</h5>  <!-- 計算機 -->  <div class="calc-wrap">    <div class="calc-in-out">      <!-- 上一條運算記錄 -->      <p class="calc-history" title=""></p>      <!-- 輸入的資料 -->      <p class="calc-in"></p>      <!-- 輸出的運算結果 -->      <p class="calc-out active"></p>    </div>    <table class="calc-operation">      <thead></thead>      <tbody>        <tr>          <td data-ac="cls" class="cls">C</td>          <td data-ac="del">←</td>          <td data-ac="sq">x<sup>2</sup></td>          <td data-ac="mul">×</td>        </tr>        <tr>          <td data-val="7">7</td>          <td data-val="8">8</td>          <td data-val="9">9</td>          <td data-ac="div">÷</td>        </tr>        <tr>          <td data-val="4">4</td>          <td data-val="5">5</td>          <td data-val="6">6</td>          <td data-ac="plus">+</td>        </tr>        <tr>          <td data-val="1">1</td>          <td data-val="2">2</td>          <td data-val="3">3</td>          <td data-ac="minus">-</td>        </tr>          <td data-ac="per">%</td>          <td data-val="0">0</td>          <td data-ac="dot">.</td>          <td data-ac="eq" class="eq">=</td>      </tbody>    </table>  </div>

2. 結合一點樣式:

body {  padding: 20px;  font-family: Arial;}.calc-wrap {  width: 300px;  border: 1px solid #ddd;  border-radius: 3px;}.calc-operation {  width: 100%;  border-collapse: collapse;}.calc-in-out {  width: 100%;  padding: 10px 20px;  text-align: right;  box-sizing: border-box;  background-color: rgba(250, 250, 250, .9);}.calc-in-out p {  overflow: hidden;  margin: 5px;  width: 100%;}.calc-history {  margin-left: -20px;  font-size: 18px;  color: #bbb;  border-bottom: 1px dotted #ddf;  min-height: 23px;}.calc-in,.calc-out {  font-size: 20px;  color: #888;  line-height: 39px;  min-height: 39px;}.calc-in {  color: #888;}.calc-out {  color: #ccc;}.calc-in.active,.calc-out.active {  font-size: 34px;  color: #666;}.calc-operation td {  padding: 10px;  width: 25%;  text-align: center;  border: 1px solid #ddd;  font-size: 26px;  color: #888;  cursor: pointer;}.calc-operation td:active {  background-color: #ddd;}.calc-operation .cls {  color: #ee8956;}

這樣靜態計算機就粗來了~~

 

3. JS邏輯

這部分就是重點了,一步步來說

首先是對計算機的監聽吧,也就是這個表格,可以使用事件委託的方式,在父級節點上監聽處理

    // 綁定事件    bindEvent: function() {      var that = this;      that.$operation.on('click', function(e) {        e = e || window.event;        var elem = e.target || e.srcElement,          val,          action;        if (elem.tagName === 'TD') {          val = elem.getAttribute('data-val') || elem.getAttribute('data-ac');  

監聽資料,擷取到的只是頁面上的某個值/操作符,所以需要將資料存放區起來形成中綴,再由中綴轉換成尾碼,最後通過尾碼進行計算

    // 中綴運算式    this.infix = [];    // 尾碼運算式    this.suffix = [];    // 尾碼運算式運算結果集    this.result = [];

按照演算法步驟,實現出來,這裡沒有使用到括弧,如果實際需要,可在相應位置修改判斷條件即可~

    // 中綴運算式轉尾碼    infix2Suffix: function() {      var temp = [];      this.suffix = [];      for (var i = 0; i < this.infix.length; i++) {        // 數值,直接壓入        if (!this.isOp(this.infix[i])) {          this.suffix.push(this.infix[i]);        }        else {          if (!temp.length) {            temp.push(this.infix[i]);          }          else {            var opTop = temp[temp.length - 1];            // 迴圈判斷運算子優先順序,將運算子較高的壓入尾碼運算式            if (!this.priorHigher(opTop, this.infix[i])) {              while (temp.length && !this.priorHigher(opTop, this.infix[i])) {                this.suffix.push(temp.pop());                opTop = temp[temp.length - 1];              }            }              // 將當前運算子也壓入尾碼運算式            temp.push(this.infix[i]);          }        }      }      // 將剩餘運算子號壓入      while (temp.length) {        this.suffix.push(temp.pop());      }    },
 // 尾碼運算式計算    calcSuffix: function() {      this.result = [];      for (var i = 0; i < this.suffix.length; i++) {        // 數值,直接壓入結果集        if (!this.isOp(this.suffix[i])) {          this.result.push(this.suffix[i]);        }        // 運算子,從結果集中取出兩項進行運算,並將運算結果置入結果集合        else {          this.result.push(this.opCalc(this.result.pop(), this.suffix[i], this.result.pop()));        }      }      // 此時結果集中只有一個值,即為結果       return this.result[0];    }

其實,在實現的時候會發現,中綴、尾碼只是一個痛點,更複雜的地方是整個計算機的狀態變化(或者說是資料變化)

在這個簡單的計算機中,就有數字(0-9)、運算子(+ - * /)、操作(清除 刪除)、預運算(百分比符號 平方)、小數點、即時運算等資料及操作

如果是科學計算機那就更複雜了,所以理清如何控制這些東西很關鍵,而其中最重要的就是中綴運算式的構建與儲存

 當連續點擊+號時,是不符合實際操作的,所以需要一個變數 lastVal 來記錄上一個值,隨著操作而更新,再通過判斷,防止程式出錯

在點擊=號之後,我們可以繼續使用這個結果進行運算,或者重新開始運算

    // 構建中綴運算式    buildInfix: function(val, type) {      // 直接的點擊等於運算之後,      if (this.calcDone) {        this.calcDone = false;        // 再點擊數字,則進行新的運算        if (!this.isOp(val)) {          this.resetData();        }        // 再點擊運算子,則使用當前的結果值繼續進行運算        else {          var re = this.result[0];          this.resetData();          this.infix.push(re);        }      }      var newVal;       ...

點擊刪除,是刪除一位元,不是直接地刪除一個數,然後更新中綴運算式的值

      // 刪除操作      if (type === 'del') {        newVal = this.infix.pop();        // 刪除末尾一位元        newVal = Math.floor(newVal / 10);        if (newVal) {          this.infix.push(newVal);        }        this.lastVal = this.infix[this.infix.length - 1];        return this.infix;      }  

而添加操作,要考慮的就更多了,比如連續的連續運算子、連續的數字、運算子+ - 接上數字表示正負數,小數點的串連存取等

      // 添加操作,首先得判斷運算子是否重複      else if (type === 'add') {        // 兩個連續的運算子        if (this.isOp(val) && this.isOp(this.lastVal)) {          return this.infix;        }        // 兩個連續的數字        else if (!this.isOp(val) && !this.isOp(this.lastVal)) {          newVal = this.lastVal * 10 + val;          this.infix.pop();          this.infix.push(this.lastVal = newVal);          return this.infix;        }        // 首個數字正負數        if (!this.isOp(val) && this.infix.length === 1 && (this.lastVal === '+' || this.lastVal === '-')) {          newVal = this.lastVal === '+' ? val : 0 - val;          this.infix.pop();          this.infix.push(this.lastVal = newVal);          return this.infix;        }        this.infix.push(this.lastVal = val);        return this.infix;      }

在很多次操作的時候,計算機都需要即時地進行運算,為簡化代碼,可以封裝成一個方法,在相應的位置調用即可

    // 即時得進行運算    calculate: function(type) {      this.infix2Suffix();      var suffixRe = this.calcSuffix();      if (suffixRe) {        this.$out.text('=' + suffixRe)          .attr('title', suffixRe)          .removeClass('active');        // 如果是直接顯示地進行等於運算        if (type === 'eq') {          this.$in.removeClass('active');          this.$out.addClass('active');          // 設定標記:當前已經顯示地進行計算          this.calcDone = true;          this.lastVal = suffixRe;          // 設定記錄          var history = this.infix.join('') + ' = ' + suffixRe;          this.$history.text(history).attr('title', history);        }      }    },

剩下的就是點擊之後的處理過程了,也就是各種調用處理 傳遞資料->構建中綴處理資料->中綴轉尾碼->尾碼運算顯示

比如點擊了數字

          // 數字:0-9          if (!isNaN(parseInt(val, 10))) {            // 構建中綴運算式並顯示            var infixRe = that.buildInfix(parseInt(val, 10), 'add');            that.$in.text(infixRe.join('')).addClass('active');            that.calculate();            return;          }

又比如幾個預運算,其實長得也差不多

       // 預運算:百分比、小數點、平方          else if (['per', 'dot', 'sq'].indexOf(action) !== -1) {            if (!that.infix.length || that.isOp(that.lastVal)) {              return;            }            if (action === 'per') {              that.lastVal /= 100;            } else if (action === 'sq') {              that.lastVal *= that.lastVal;            } else if (action === 'dot') {              // that.curDot = true;            }            // 重新構建中綴運算式            var infixRe = that.buildInfix(that.lastVal, 'change');            that.$in.text(infixRe.join('')).addClass('active');            that.calculate();          }

以上就是這個簡單計算機的實現步驟了,變化太多還不敢保證不會出錯

基本邏輯如此,如果要加上小數點運算、括弧運算、正餘弦等科學計算機的功能,還是自己去實現吧。。腦大啊。。 

$(function() {  function Calculator($dom) {    this.$dom = $($dom);    // 曆史運算    this.$history = this.$dom.find('.calc-history');    // 輸入區    this.$in = this.$dom.find('.calc-in');    // 輸出區    this.$out = this.$dom.find('.calc-out');    this.$operation = this.$dom.find('.calc-operation');    // 運算子映射    this.op = {      'plus': '+',      'minus': '-',      'mul': '*',      'div': '/'    };    this.opArr = ['+', '-', '*', '/'];    // 中綴運算式    this.infix = [];    // 尾碼運算式    this.suffix = [];    // 尾碼運算式運算結果集    this.result = [];    // 儲存最近的值    this.lastVal = 0;    // 當前已經計算等於完成    this.calcDone = false;    // 當前進行中小數點點(.)相關值的修正    this.curDot = false;    this.init();  }  Calculator.prototype = {    constructor: Calculator,    // 初始化    init: function() {      this.bindEvent();    },    // 綁定事件    bindEvent: function() {      var that = this;      that.$operation.on('click', function(e) {        e = e || window.event;        var elem = e.target || e.srcElement,          val,          action;        if (elem.tagName === 'TD') {          val = elem.getAttribute('data-val') || elem.getAttribute('data-ac');          // 數字:0-9          if (!isNaN(parseInt(val, 10))) {            // 構建中綴運算式並顯示            var infixRe = that.buildInfix(parseInt(val, 10), 'add');            that.$in.text(infixRe.join('')).addClass('active');            that.calculate();            return;          }          action = val;          // 操作:清除、刪除、計算等於          if (['cls', 'del', 'eq'].indexOf(action) !== -1) {            if (!that.infix.length) {              return;            }            // 清空資料            if (action === 'cls' || (action === 'del' && that.calcDone)) {              that.$in.text('');              that.$out.text('');              that.resetData();            }            // 清除            else if (action === 'del') {              // 重新構建中綴運算式              var infixRe = that.buildInfix(that.op[action], 'del');              that.$in.text(infixRe.join('')).addClass('active');              that.calculate();            }            // 等於            else if (action === 'eq') {              that.calculate('eq');            }          }          // 預運算:百分比、小數點、平方          else if (['per', 'dot', 'sq'].indexOf(action) !== -1) {            if (!that.infix.length || that.isOp(that.lastVal)) {              return;            }            if (action === 'per') {              that.lastVal /= 100;            } else if (action === 'sq') {              that.lastVal *= that.lastVal;            } else if (action === 'dot') {              // that.curDot = true;            }            // 重新構建中綴運算式            var infixRe = that.buildInfix(that.lastVal, 'change');            that.$in.text(infixRe.join('')).addClass('active');            that.calculate();          }          // 運算子:+ - * /          else if (that.isOp(that.op[action])) {            if (!that.infix.length && (that.op[action] === '*' || that.op[action] === '/')) {              return;            }            var infixRe = that.buildInfix(that.op[action], 'add');            that.$in.text(infixRe.join('')).addClass('active');          }        }      });    },    resetData: function() {      this.infix = [];      this.suffix = [];      this.result = [];      this.lastVal = 0;      this.curDot = false;    },    // 構建中綴運算式    buildInfix: function(val, type) {      // 直接的點擊等於運算之後,      if (this.calcDone) {        this.calcDone = false;        // 再點擊數字,則進行新的運算        if (!this.isOp(val)) {          this.resetData();        }        // 再點擊運算子,則使用當前的結果值繼續進行運算        else {          var re = this.result[0];          this.resetData();          this.infix.push(re);        }      }      var newVal;      // 刪除操作      if (type === 'del') {        newVal = this.infix.pop();        // 刪除末尾一位元        newVal = Math.floor(newVal / 10);        if (newVal) {          this.infix.push(newVal);        }        this.lastVal = this.infix[this.infix.length - 1];        return this.infix;      }      // 添加操作,首先得判斷運算子是否重複      else if (type === 'add') {        // 兩個連續的運算子        if (this.isOp(val) && this.isOp(this.lastVal)) {          return this.infix;        }        // 兩個連續的數字        else if (!this.isOp(val) && !this.isOp(this.lastVal)) {          newVal = this.lastVal * 10 + val;          this.infix.pop();          this.infix.push(this.lastVal = newVal);          return this.infix;        }        // 首個數字正負數        if (!this.isOp(val) && this.infix.length === 1 && (this.lastVal === '+' || this.lastVal === '-')) {          newVal = this.lastVal === '+' ? val : 0 - val;          this.infix.pop();          this.infix.push(this.lastVal = newVal);          return this.infix;        }      // TODO: 小數點運算      //   if (this.isOp(val)) {      //     this.curDot = false;      //   }      //   // 小數點      //   if (this.curDot) {      //     var dotLen = 0;      //     newVal = this.infix.pop();      //     dotLen = newVal.toString().split('.');      //     dotLen = dotLen[1] ? dotLen[1].length : 0;      //     newVal += val / Math.pow(10, dotLen + 1);      //     // 修正小數點運算精確值      //     newVal = parseFloat(newVal.toFixed(dotLen + 1));      //     this.infix.push(this.lastVal = newVal);      //     return this.infix;      //   }        this.infix.push(this.lastVal = val);        return this.infix;      }      // 更改操作,比如%的預運算      else if (type === 'change') {        this.infix.pop();        this.infix.push(this.lastVal = val);        return this.infix;      }    },    // 判斷是否為運算子    isOp: function(op) {      return op && this.opArr.indexOf(op) !== -1;    },    // 判斷運算子優先順序    priorHigher: function(a, b) {      return (a === '+' || a === '-') && (b === '*' || b === '/');    },    // 進行運算子的運算    opCalc: function(b, op, a) {      return op === '+'        ? a + b        : op === '-'        ? a - b        : op === '*'        ? a * b        : op === '/'        ? a / b        : 0;    },    // 即時得進行運算    calculate: function(type) {      this.infix2Suffix();      var suffixRe = this.calcSuffix();      if (suffixRe) {        this.$out.text('=' + suffixRe)          .attr('title', suffixRe)          .removeClass('active');        // 如果是直接顯示地進行等於運算        if (type === 'eq') {          this.$in.removeClass('active');          this.$out.addClass('active');          // 設定標記:當前已經顯示地進行計算          this.calcDone = true;          this.lastVal = suffixRe;          // 設定記錄          var history = this.infix.join('') + ' = ' + suffixRe;          this.$history.text(history).attr('title', history);        }      }    },    // 中綴運算式轉尾碼    infix2Suffix: function() {      var temp = [];      this.suffix = [];      for (var i = 0; i < this.infix.length; i++) {        // 數值,直接壓入        if (!this.isOp(this.infix[i])) {          this.suffix.push(this.infix[i]);        }        else {          if (!temp.length) {            temp.push(this.infix[i]);          }          else {            var opTop = temp[temp.length - 1];            // 迴圈判斷運算子優先順序,將運算子較高的壓入尾碼運算式            if (!this.priorHigher(opTop, this.infix[i])) {              while (temp.length && !this.priorHigher(opTop, this.infix[i])) {                this.suffix.push(temp.pop());                opTop = temp[temp.length - 1];              }            }              // 將當前運算子也壓入尾碼運算式            temp.push(this.infix[i]);          }        }      }      // 將剩餘運算子號壓入      while (temp.length) {        this.suffix.push(temp.pop());      }    },    // 尾碼運算式計算    calcSuffix: function() {      this.result = [];      for (var i = 0; i < this.suffix.length; i++) {        // 數值,直接壓入結果集        if (!this.isOp(this.suffix[i])) {          this.result.push(this.suffix[i]);        }        // 運算子,從結果集中取出兩項進行運算,並將運算結果置入結果集合        else {          this.result.push(this.opCalc(this.result.pop(), this.suffix[i], this.result.pop()));        }      }      // 此時結果集中只有一個值,即為結果       return this.result[0];    }  };  new Calculator('.calc-wrap');});

以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支援幫客之家。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.