JS原生——计算器(非eval,附源码)

一、前言

主要功能:

带括号的加减乘除运算、清空、清空末位、历史记录、字体切换、主题切换。

计算逻辑:

主要利用弹栈的思想实现:

  1. 定义calculateExpression函数,接受一个表达式作为参数。

  2. 如果表达式中包含了括号,则使用递归的方式处理括号内的子表达式。具体来说,首先找到左括号的位置left和右括号的位置right,然后将括号内的子表达式提取出来,并调用自身并传入子表达式作为参数,得到子表达式的计算结果subResult。接着,将子表达式的计算结果替换掉原来的子表达式,并继续进行下一轮的计算,直到所有的括号内的子表达式都被计算完。

  3. 定义一个对象operators,存储了不同操作符的优先级。同时,初始化两个栈,operatorStack用于存储操作符,numberStack用于存储操作数。

  4. 遍历表达式中的每个字符,如果遇到的字符不是操作符,则将其添加到number字符串中,表示当前正在读取一个操作数。如果遇到操作符,则将之前读取的操作数转换为浮点数并压入numberStack中,并清空number字符串。

  5. 通过比较操作符的优先级,决定是否需要进行计算。如果操作符栈中的栈顶操作符的优先级大于等于当前操作符的优先级,就进行计算。计算时会调用calc函数,从numberStack中弹出两个操作数和一个操作符,并根据操作符进行相应的运算,将运算结果压入numberStack中。最后,将当前操作符压入operatorStack中。

  6. 完成遍历后,将最后一个操作数转换为浮点数并压入numberStack中。然后,通过循环计算剩余的操作符,直到操作符栈为空。

  7. 返回numberStack中唯一的元素,即为最终的计算结果。

  8. 监听等号按钮的点击事件,在点击时获取输入框中的表达式,调用calculateExpression函数计算结果,并将结果显示在输出框中。同时,将表达式和结果添加到历史记录中,并更新历史记录的显示。

这就是博主利用弹栈的思想实现计算器的基本思路。

二、项目运行效果     

JS原生——计算器(非eval,附源码)_第1张图片

                                 

三、项目结构 

JS原生——计算器(非eval,附源码)_第2张图片

img里的图片是iconfont图标下面是下载地址(可下载自己喜欢的图标):https://www.iconfont.cn/search/index?searchType=icon&q=%E5%8F%89%E5%8F%89

四、全部代码

HTML:




    
    
    计算器
    


    

  • 粉色 蓝色 绿色 橙色

  • 隶书 幼圆 楷体 雅黑

CSS:

*{
    margin: 0;
    padding: 0;
    text-decoration: none;
    list-style: none;
    background-repeat: no-repeat;
    font-family: var(--font);
    color:#f48b8f;
    font-size: 20px;
    font-weight: 600;
}
:root{
    --font1:'LiSu';
    --font2:'YouYuan';
    --font3:'KaiTi';
    --font4:'Microsoft YaHai';

    --color1: #f9dbde;
    --color2: #87ceeb;
    --color3: #90ee90;
    --color4: #ff7f50;
  
    --fontcolor1: #000; 
    --fontcolor2: #fff;
    --fontcolor3: #000;
    --fontcolor4: #fff;

    --button-bg1: #fbfaf8;
    --button-bg2: #87ceeb;
    --button-bg3: #90ee90;

    --color: var(--color1);
    --button-bg: var(--button-bg1);
}

.calculator{
    width: 450px;
    height: 750px;
    margin: 50px auto;
    padding-top: 30px;
    background-color: var(--color);
    border-radius: 30px;
    box-shadow: inset 0px 0px 20px #ffa4a2, 
    inset -5px -5px 6px #e7b9b9;
}
.inp{
    display: flex;
    width: 85%;
    height: 200px;
    margin: 0 auto;
    border-radius: 30px;
    margin-bottom: 30px;
    background: linear-gradient(to bottom,#fff,#FFE7E7);
    align-items: center;
}
.gradualBox{
    width: 97%;
    height: 95%;
    margin: 0 auto;
    border-radius: 30px;
    background: linear-gradient(to bottom,#FFE7E7,#fff);
}
.inp .history{
    width: 100%;
    height: 150px;
    overflow-y: auto;
    white-space: pre-wrap;
    text-align: right;
    padding-right: 25px;
    padding-top: 15px;
    box-sizing: border-box;
    line-height: 17px;
    border-radius: 30px 30px 0 0;
}
.history::-webkit-scrollbar {
    width: 0.1px; 
}
.inp .output{
    width: 100%;
    height: 50px;
    text-align: right;
    padding-right: 25px;
    padding-bottom: 10px;
    box-sizing: border-box;
    font-size: 24px;
    font-weight: 800;
    border: none;
    background-color: transparent;
    outline: none;
}
.theme{
    width: 100%;
    height: 60px;
    display: flex;
    gap: 15px;
    justify-content: center;
}
.theme ul{
    width: 90px;
    height: 60px;
    border: none;
    border-radius: 20px;
    background: #fbfaf8;
    box-shadow: inset 5px 5px 6px #e5d3d4,
                inset -5px -5px 6px #ffffff;
}
.theme ul>button{
    width: 100%;
    height: 100%;
    border: none;
    border-radius: 20px;
    background: #fbfaf8;
    box-shadow: inset 5px 5px 6px #ffcfd2,
                inset -5px -5px 6px #ffffff;
    background: linear-gradient(to bottom,#fff,#fff);
    cursor: pointer;
}
.theme li{
    width: 350px;
    height: 320px;
    position: relative;
    left: -180px;
    top: 5px;
    background-color: white;
    display: none;
    opacity: 0.75;
    
}
.theme .prompt i{
    display: block;
    width: 25px; 
    height: 25px; 
    position: absolute;
    right: 0;
}
.theme .prompt p{
    text-align: center;
    padding: 90px 0;
    box-sizing: border-box;
}
.theme .prompt span{
    cursor: pointer;
}
.theme .prompt input{
    width: 70%;
    height: 50px;
    text-align: center;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%,0);
}
.Fontcancel{
    position: absolute;
    left: 50px;
    bottom: 50px;
}
.highlight {
    background-color: pink;
  }  
.Fontconfirm{
    position: absolute;
    right: 50px;
    bottom: 50px;
}
.Colorcancel{
    position: absolute;
    left: 50px;
    bottom: 50px;
}
.Colorconfirm{
    position: absolute;
    right: 50px;
    bottom: 50px;
}
.number{
    width: 100%;
    height: 470px;
    display: flex;
    gap: 15px;
    align-content: center;
    flex-wrap: wrap;
    padding: 22.5px;
    box-sizing: border-box;
    margin-top: -10px;
    border-radius: 30px;

}
.number button{
    width: 90px;
    height: 70px;
    border: none;
    border-radius: 20px;
    background: #fbfaf8;
    box-shadow: inset 5px 5px 6px #ffd5d8, 
    inset -5px -5px 6px #ffffff;
    background: linear-gradient(to bottom,#FFE7E7,#fff);
    background: var(--button-bg);
    cursor: pointer;
}
.number #str{
    box-shadow: inset 5px 5px 6px #ffa4a2, 
    inset -5px -5px 6px #e7b9b9;
    background: linear-gradient(to bottom,#fbb6b5,#fececa);
    color: #fff;;
}

JS:

let history = [];
const historyLimit = 10;
const output = document.querySelector('.output');
const historyDiv = document.querySelector('.history');
const numberButtons = Array.from(document.querySelectorAll('.number button')); 
const acButton = numberButtons.filter(button => button.textContent === "AC")[0];
const cButton = numberButtons.filter(button => button.textContent === "C")[0];
const equalsButton = numberButtons.filter(button => button.textContent === "=")[0];
numberButtons.forEach(button => {
    button.addEventListener('click', () => {
        if (button.textContent !== 'AC' && button.textContent !== 'C' && button.textContent !== '=') {
            output.value += button.textContent;
        }
    });
});
acButton.addEventListener('click', () => {
    output.value = '';
});
cButton.addEventListener('click', () => {
    output.value = output.value.slice(0, -1);
});
function calculateExpression(expression) {
    if (expression.includes('(')) {
        let left = expression.indexOf('(');
        console.log(left);
        let right = expression.indexOf(')');
        console.log(right);
        let subExp = expression.substring(left + 1, right);
        let subResult = calculateExpression(subExp);
        console.log('subResult:' + subResult);
        expression = expression.substring(0, left) + subResult + expression.substring(right + 1);
        return calculateExpression(expression);
    }
    const operators = {
        '+': 1,
        '-': 1,
        '×': 2,
        '÷': 2,
    };
    let operatorStack = [];
    let numberStack = [];
    let number = '';
    
    for (let i = 0; i < expression.length; i++) {
        let char = expression[i];
        if (!operators[char]) {
            number += char;
            console.log('num1:' + number);
            continue;
        } else {
            if (char === '-' && (i === 0 || expression[i - 1] === '(')) {
                number += char;
                console.log('num2:' + number);
                continue;
            }
            numberStack.push(parseFloat(number));
            console.log('numStack1:' + number);
            number = '';
        }
        
        while (operatorStack.length && operators[char] <= operators[operatorStack[operatorStack.length - 1]]) {
            calc(numberStack, operatorStack);
        }
        operatorStack.push(char);
        console.log('operatorStack1:' + operatorStack);
    }
        numberStack.push(parseFloat(number));
        console.log('numberStack1:' + numberStack);
        while (operatorStack.length) {
            calc(numberStack, operatorStack);
        }
        
    return numberStack[0];
}

function calc(numberStack, operatorStack) {
    const number2 = numberStack.pop();
    console.log('Calcnumber2:' + number2);
    const number1 = numberStack.pop();
    console.log('Calcnumber1:' + number1);
    const operator = operatorStack.pop();
    console.log(typeof(number1));;
    let result;
    switch (operator) {
        case '+':
            result = number1 + number2;
            break;
        case '-':
            result = number1 - number2;
            break;
        case '×':
            result = number1 * number2;
            break;
        case '÷':
            result = number1 / number2;
            break;
    }
    numberStack.push(result);
}

equalsButton.addEventListener('click', () => {
    const expression = output.value;
    const result = calculateExpression(expression);
    output.value = result;
    if (history.length >= historyLimit) {
        history.shift();
    } else {
        const historyEntry = expression + ' = ' + result + '\n';
        history.push(historyEntry);
        historyDiv.innerText = history.join('\n');
    }
});

// 清除历史
const clearHistoryBtn = document.querySelector('.ClearHistory');
clearHistoryBtn.addEventListener('click', () => {
    history = [];
    historyDiv.innerText = '';
});
// 数字转中文数字
function convertToChinese(num) {
    const map = {
        0: '零', 1: '壹', 2: '贰', 3: '叁', 4: '肆', 5: '伍',
        6: '陆', 7: '柒', 8: '捌', 9: '玖'
    };
    let converted = '';
    for (let i = 0; i < num.length; i++) {
        converted += map[num[i]];
    }
    return converted;
}
const convertBtn = document.getElementById('convert');
const Convertprompt = document.querySelector('.convert .prompt');
const convertedInput = document.getElementById('converted');
const closeIcon = document.querySelector('.convert .prompt i');
convertBtn.addEventListener('click', () => {
    Convertprompt.style.display = 'block';
    if (output.value.trim() === '') {
        document.querySelector('.convert .prompt p').innerText = '请填写数字';
        return;
    } else if (output.value.trim() === 'Error') {
        document.querySelector('.convert .prompt p').innerText = '结果错误,请重新输入!';
    }
    else if (output.value.trim() !== '') {
        document.querySelector('.convert .prompt p').innerText = '大写转换成功';
    }
    // 转换显示
    const result = output.value;
    convertedInput.value = convertToChinese(result);
});
closeIcon.addEventListener('click', () => {
    Convertprompt.style.display = 'none';
});
// 字体切换
const ThemeFontBtn = document.getElementById('ThemeFont');
const Fontprompt = document.querySelector('.ThemeFont .prompt');
const FontIcon = document.querySelector('.ThemeFont .prompt i');
const Fontcancel = document.querySelector('.Fontcancel');
const fontBtns = document.querySelectorAll('.ThemeFont .prompt p span');
const Fontconfirm = document.querySelector('.Fontconfirm');
const root = document.documentElement;
ThemeFontBtn.addEventListener('click', () => {
    Fontprompt.style.display = 'block';
});
FontIcon.addEventListener('click', () => {
    Fontprompt.style.display = 'none';
});
Fontcancel.addEventListener('click', () => {
    Fontprompt.style.display = 'none';
});
Fontconfirm.addEventListener('click', () => {
    Fontprompt.style.display = 'none';
});
const font1Btn = document.getElementById('font1');
const font2Btn = document.getElementById('font2');
const font3Btn = document.getElementById('font3');
const font4Btn = document.getElementById('font4');
font1Btn.addEventListener('click', () => {
    root.style.setProperty('--font', 'var(--font1)');
});
font2Btn.addEventListener('click', () => {
    root.style.setProperty('--font', 'var(--font2)');
});
font3Btn.addEventListener('click', () => {
    root.style.setProperty('--font', 'var(--font3)');
});
font4Btn.addEventListener('click', () => {
    root.style.setProperty('--font', 'var(--font4)');
});
fontBtns.forEach(btn => {
    btn.addEventListener('click', () => {
        fontBtns.forEach(b => b.classList.remove('highlight'));
        btn.classList.add('highlight');
    });
});
// 主题变换
const ThemeColorprompt = document.querySelector('.ThemeColor .prompt');
const ThemeColor = document.getElementById('ThemeColor');
const ThemeColorIcon = document.querySelector('.ThemeColor .prompt i');
const ThemeColorcancel = document.querySelector('.Colorcancel');
const ThemeColorconfirm = document.querySelector('.Colorconfirm');
ThemeColor.addEventListener('click', () => {
    ThemeColorprompt.style.display = 'block';
});
ThemeColorIcon.addEventListener('click', () => {
    ThemeColorprompt.style.display = 'none';
});
ThemeColorcancel.addEventListener('click', () => {
    ThemeColorprompt.style.display = 'none';
});
ThemeColorconfirm.addEventListener('click', () => {
    ThemeColorprompt.style.display = 'none';
});
// 获取颜色按钮
const colorBtns = document.querySelectorAll('.ThemeColor .prompt span');

// 遍历颜色按钮添加点击监听  
colorBtns.forEach(btn => {
    btn.addEventListener('click', () => {
        if (btn.id === 'Color1') {
            root.style.setProperty('--color', 'var(--color1)');
            root.style.setProperty('--fontcolor', 'var(--fontcolor1)');
        } else if (btn.id === 'Color2') {
            root.style.setProperty('--color', 'var(--color2)');
            root.style.setProperty('--fontcolor', 'var(--fontcolor2)');
        } else if (btn.id === 'Color3') {
            root.style.setProperty('--color', 'var(--color3)');
            root.style.setProperty('--fontcolor', 'var(--fontcolor3)');
        } else if (btn.id === 'Color4') {
            root.style.setProperty('--color', 'var(--color4)');
            root.style.setProperty('--fontcolor', 'var(--fontcolor4)');
        }
    });
});
function initTheme() {
    // 设置粉色主题
    root.style.setProperty('--color', 'var(--color1)');
    root.style.setProperty('--button-bg', 'var(--button-bg1)');
}
// 页面加载完就调用  
window.onload = initTheme;

五、答疑解惑

        这是一个非常适合练习JavaScript的小案例,博主在测试时没有任何问题,如果大家测试出bug或代码有什么不懂的地方都可以随时评论留言或联系博主QQ。

还多请各位小可爱多多点赞支持,你们的支持是我最大的动力

博主QQ:1196094293

谢谢各位的支持~~

你可能感兴趣的:(web,web,javascript,html,css)