js實現四則計算(中綴,字尾表示式)

風靈使發表於2018-12-31

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>蘋果風格的計算器</title>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <link rel="stylesheet" media="screen and (max-width: 500px)" href="index-mobile.css">
    <link rel="stylesheet" media="screen and (min-width: 501px)" href="index-pc.css">
</head>

<body>
    <div class="calculator">
        <div class="operation">
            <div class="screen">
                <!-- 上一條運算記錄 -->
                <div style="display: none;" class="calc-history"></div>
                <!-- 輸入的資料 -->
                <div class="calc-in"></div>
                <!-- 輸出的運算結果 -->
                <div class="calc-out active"></div>
               
            </div>
            <div class="line">
                <div data-ac="cls" class="button symbol">C</div>
                <div data-ac="per" class="button symbol">%</div>
                <div data-ac="sq" class="button symbol">x
                    <span>2</span>
                </div>
                <div data-ac="mul" class="button operator">&times;</div>
            </div>
            <div class="line">
                <div data-val="7" class="button number">7</div>
                <div data-val="8" class="button number">8</div>
                <div data-val="9" class="button number">9</div>
                <div data-ac="div" class="button operator">&divide;</div>
            </div>
            <div class="line">
                <div data-val="4" class="button number">4</div>
                <div data-val="5" class="button number">5</div>
                <div data-val="6" class="button number">6</div>
                <div data-ac="plus" class="button operator">+</div>
            </div>
            <div class="line">
                <div data-val="1" class="button number">1</div>
                <div data-val="2" class="button number">2</div>
                <div data-val="3" class="button number">3</div>
                <div data-ac="minus" class="button operator">-</div>
            </div>
            <div class="line">
                <div data-val="0" data-val="0" class="button grow number">0</div>
                <div data-ac="dot" class="button operator">.</div>
                <div data-ac="eq" class="button operator">=</div>
            </div>
        </div>
    </div>
    <p class="description">一個簡易計算器<br>可以進行四則運算</p>
    <script src="index.js"></script>
</body>

</html>

index.js

$(function () {
    // console.log('文件載入成功')
    // $('.operation').on('click', (e) => {
    //     e = e || window.event
    //     let elem = e.target || e.srcElement
    //     let val = elem.getAttribute('data-val') || elem.getAttribute('data-ac')
    //     console.log(val)
    //     if (val) {
    //         console.log(val)
    //     }
    // })
    class Calculator {
        constructor($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('.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()
        }
        init() {
            this.bindEvents()
        }
        bindEvents() {
            this.$operation.on('click', (e) => {
                e = e || window.event
                let elem = e.target || e.srcElement
                let val = elem.getAttribute('data-val') || elem.getAttribute('data-ac')
                let action
                if (val) {
                    // 數字 0-9
                    if (!isNaN(parseInt(val, 10))) {
                        let num = parseInt(val, 10)
                        // 構建中綴表示式
                        let infixRe = this.buildInfix(num,'add')
                        console.log('中綴表示式--',infixRe)
                        this.$in.text(infixRe.join('')).addClass('active')
                        this.calculate()
                        return
                    }
                    action = val
                    // 操作:清除、刪除、等於
                    if (['cls','del','eq'].indexOf(action) !== -1) {
                        console.log('點選了運算子')
                        console.log(action)
                        if (!this.infix.length) {
                            console.log('中綴表示式不存在就點了運算子')
                            return
                        }
                        // 清空資料
                        if (action === 'cls' || (action === 'del' && this.calcDone)) {
                            console.log('清空運算區域')
                            this.$in.text('')
                            this.$out.text('')
                            this.resetData()
                        }
                        // 清除
                        else if (action === 'del') {
                            return
                            console.log('清除鍵')
                            console.log(this.op[action])
                            let infixRe = this.buildInfix(this.op[action],'del')
                            this.$in.text(infixRe.join('')).addClass('active')
                            this.calculate()
                        }
                        // 等於
                        else if (action === 'eq') {
                            this.calculate('eq')
                        }
                    }
                    else if (['per','dot','sq'].indexOf(action) !== -1) {
                        if (!this.infix.length || this.isOp(this.lastVal)) {
                            return
                        }
                        if (action === 'per') {
                            this.lastVal /= 100 
                        } else if (action === 'dot') {
                            this.curDot = true
                        } else if (action === 'sq') {
                            this.lastVal *= this.lastVal
                        }
                        // 重新構建中綴表示式
                        let infixRe = this.buildInfix(this.lastVal,'change')
                        this.$in.text(infixRe.join('')).addClass('active')
                        this.calculate()
                    }
                    // 運算子:+ - * /
                    else if (this.isOp(this.op[action])) {
                        if (!this.infix.length && (this.op[action] === '+' || this.op[action] === '/')) {
                            return
                        }
                        console.log(action)
                        let infixRe = this.buildInfix(this.op[action],'add')
                        console.log(infixRe)
                        this.$in.text(infixRe.join('')).addClass('active')
                    }
                }
            })
        }
        // 構建中綴表示式
        buildInfix(val, type) {
            console.log('構建中綴表示式')
            console.log(val)
            console.log(type)
            // 直接點選'='運算之後
            if (this.calcDone) {
                console.log('直接點選運算之後')
                this.calcDone = false
                if (!this.isOp(val)) {
                    // 如果再點選數字,則進行新的運算
                    this.resetData()
                } else {
                    // 再點選運算子,則使用當前的結果值繼續進行運算
                    let re = this.result[0]
                    this.resetData()
                    this.infix.push(re)
                }
            }
            let newVal
            // 刪除操作
            if (type === 'del') {
                console.log('刪除操作')
                newVal = this.infix.pop()
                console.log('新值為--',newVal)
                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') {
                console.log('新增操作')
                console.log(val)
                console.log(this.lastVal)
                console.log(this.isOp(val))
                console.log(this.isOp(this.lastVal))
                // 兩個連續的運算子
                if (this.isOp(val) && this.isOp(this.lastVal)) {
                    console.log('運算子')
                    console.log(this.infix)
                    return this.infix
                }
                // 兩個連續的數字
                else if (!this.isOp(val) && !this.isOp(this.lastVal)) {
                    console.log('兩個數字')
                    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 === '-')) {
                    console.log('首個數字正負數')
                    newVal = this.lastVal === '+' ? val : 0 - val
                    this.infix.pop()
                    this.infix.push(this.lastVal = newVal)
                    return this.infix
                }
                // // TODO:小數點運算
                // else if (this.isOp(val)) {
                //     this.curDot = false
                // }
                // // 小數點
                // if (this.curDot) {
                //     let 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') {
                console.log('更改操作')
                this.infix.pop()
                this.infix.push(this.lastVal = val)
                return this.infix
            }
        }
        // 中綴表示式轉字尾
        infix2suffix() {
            let temp = []
            this.suffix = []
            for (let i =0;i < this.infix.length;i++) {
                // 數值直接壓入
                if (!this.isOp(this.infix[i])) {
                    this.suffix.push(this.infix[i])
                } else {
                    // temp陣列為空遇到操作符時直接壓入
                    if (!temp.length) {
                        temp.push(this.infix[i])
                    } else {
                        let 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() {
            this.result = []
            for (let 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]
        }
        // 判斷是否為運算子
        isOp(val) {
            return val && this.opArr.indexOf(val) !== -1
        }
        // 判斷運算子優先順序
        priorHigher(a, b) {
            return (a === '+' || a === '-') && (b === '*' || b === '/')
        }
        // 進行運算子的運算
        opCalc(b, op, a) {
            return op === '+' ?
                a + b :
                op === '-' ?
                a - b :
                op === '*' ?
                a * b :
                op === '/' ?
                a / b :
                0
        }
        // 即時得進行運算
        calculate(type) {
            this.infix2suffix()
            let 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
                    // 設定歷史記錄
                    let history = this.infix.join('') + ' = ' + suffixRe
                    this.$history.text(history)
                                 .attr('title',history)
                }
            }
        }
        // 清空資料
        resetData() {
            this.infix = []
            this.suffix = []
            this.result = []
            this.lastVal = 0
            this.curDot = false
        }
    }
    let calculator = new Calculator('.calculator')
    console.log(calculator)
})

index-mobile.css

body {
    margin: 0;
    padding: 0;
}
div {
    box-sizing: border-box;
}
.description {
    display: none;
}
.calculator {
    width: 100vw;
    height: 100vh;
    background: black;
    position: relative;
}
.screen {
    width: 100%;
    margin-bottom: 24px;
}
.screen>div {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    padding: 4px 4vw;
    font-size: 28px;
    color: #aaa;
    margin-bottom: 4px;
}
.screen>div.active {
    color: red;
}
.operation {
    width: 100%;
    position: absolute;
    bottom: 0;
}
.line {
    display: flex;
    flex-direction: row;
    justify-content: space-around;
    align-items: center;
    margin: 4px 0;
}
.button {
    /* flex-grow: 1; */
    background: #999;
    width: 20vw;
    height: 20vw;
    position: relative;
    display: flex;
    justify-content: center;
    align-items: center;
    border-radius: 50%;
    font-size: 36px;
    color: white;
}
.button.grow {
    /* flex-grow: 2; */
    width: 46vw;
    height: 20vw;
    border-radius: 18vw;
}
.button:hover {
    color: red;
}
.button.symbol {
    color: black;
}
.button.number {
    background: #333;
}
.button.operator {
    background: orange;
}
.button>span {
    font-size: 18px;
    /* background: red; */
    position: absolute;
    left: 62%;
    top: 16%;
}

index-pc.css

body {
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    background: skyblue;
}
div {
    box-sizing: border-box;
}
.description {
   font-size: 36px;
   margin-left: 12px;
}
.calculator {
    width: 375px;
    height: 580px;
    background: black;
    position: relative;
    border-radius: 12px;
}
.screen {
    width: 100%;
    margin-bottom: 24px;
}
.screen>div {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    padding: 4px 4vw;
    font-size: 28px;
    color: #aaa;
    margin-bottom: 4px;
}
.screen>div.active {
    color: red;
}
.operation {
    width: 100%;
    position: absolute;
    bottom: 0;
}
.line {
    display: flex;
    flex-direction: row;
    justify-content: space-around;
    align-items: center;
    margin: 4px 0;
}
.button {
    /* flex-grow: 1; */
    background: #999;
    width: 75px;
    height: 75px;
    position: relative;
    display: flex;
    justify-content: center;
    align-items: center;
    border-radius: 50%;
    font-size: 36px;
    color: white;
}
.button.grow {
    /* flex-grow: 2; */
    width: 172.5px;
    height: 75px;
    border-radius: 18vw;
}
.button:hover {
    color: red;
    cursor: pointer;
}
.button.symbol {
    color: black;
}
.button.number {
    background: #333;
}
.button.operator {
    background: orange;
}
.button>span {
    font-size: 18px;
    /* background: red; */
    position: absolute;
    left: 62%;
    top: 16%;
}

截圖

在這裡插入圖片描述

相關文章