练手:JS计算器(第二版)
本帖最后由 马黑黑 于 2024-1-3 08:37 编辑 <br /><br /><style>#ma { margin: 50px auto 0; padding: 8px; width: fit-content; max-width: 256px; box-sizing: border-box; height: fit-content; border-radius: 10px; background: linear-gradient(to right bottom, #666,tan); box-shadow: 2px 4px 8px black; }
#calcMsg, #calcRes { padding: 6px; color: #eee; font-size: 18px; word-break: break-all; }
#calcRes { text-align: right; color: #000; font-size: 20px; background: #eee; }
#calc { display: grid; place-items: center; grid-template-columns: repeat(4, 60px); grid-template-rows: repeat(5, 50px); }
.btn, .clear { width: 40px; height: 40px; border-radius: 50%; background: #aaa; box-shadow: inset 0 0 10px #000; font: normal 20px/40px sans-serif; text-align: center; user-select: none; cursor: pointer; }
.btn:hover, .clear:hover { background: #ccc; }
.num { color: darkred; font-weight: bold; }
</style>
<div id="ma">
<div id="calcMsg">计算器</div>
<div id="calcRes">0</div>
<div id="calc">
<div class="clear" id="clear">C</div>
<div class="btn" data-idx="(">(</div>
<div class="btn" data-idx=")">)</div>
<div class="clear" id="del">←</div>
<div class="btn num" data-idx="7">7</div>
<div class="btn num" data-idx="8">8</div>
<div class="btn num" data-idx="9">9</div>
<div class="btn" data-idx="/">÷</div>
<div class="btn num" data-idx="4">4</div>
<div class="btn num" data-idx="5">5</div>
<div class="btn num" data-idx="6">6</div>
<div class="btn" data-idx="*">×</div>
<div class="btn num" data-idx="1">1</div>
<div class="btn num" data-idx="2">2</div>
<div class="btn num" data-idx="3">3</div>
<div class="btn num" data-idx="-">-</div>
<div class="btn num" data-idx="0">0</div>
<div class="btn" data-idx=".">.</div>
<div class="btn" id="percent">%</div>
<div class="btn" data-idx="+">+</div>
<div class="clear" id="pow2">x²</div>
<div class="clear" id="pow3">x³</div>
<div class="clear" id="sqrt">√</div>
<div class="clear" id="equal" data-idx="=">=</div>
</div>
</div>
<script>
let btns = document.querySelectorAll('.btn'); /* 非id运算类按钮 */
/* 运算函数 */
let _eval = (fn) => new Function('return ' + fn)();
/* 处理运算结果 */
let calc = (str) => {
let res;
try {
res = _eval(str);
res = res == Infinity ? '∞' : num2Fixed(res);
} catch(err) {
res = '算式非法';
}
return res;
};
/* 非id类按钮点击事件 */
btns.forEach(item => {
item.onclick = () => {
if(calcRes.innerText === '0') {
if(item.dataset.idx === '0') {
calcRes.innerText = '0';
}else{
calcRes.innerText = isNaN(parseInt(item.dataset.idx)) && item.dataset.idx !== '(' ? '0' + item.dataset.idx : item.dataset.idx;
}
}else{
calcRes.innerText += item.dataset.idx;
}
calcMsg.innerText = calcRes.innerText;
}
});
/* C按钮点击事件 */
clear.onclick = () => {
calcMsg.innerText = '计算器';
calcRes.innerText = '0';
};
/* =按钮点击事件 */
equal.onclick = () => {
let result = calc(calcRes.innerText);
if(result === '算式非法') {
calcMsg.innerText = result;
}else{
calcRes.innerText = result;
}
if(calcMsg.innerText.lastIndexOf('=') < 0) calcMsg.innerText += '=';
};
/* ←按钮点击事件 */
del.onclick = () => {
calcRes.innerText = calcRes.innerText.slice(0,-1) || 0;
calcMsg.innerText = calcRes.innerText;
};
/* 开方&幂运算按钮点击事件 */
.forEach(elm => {
elm.onclick = () => {
let str = calcRes.innerText;
switch (elm.id) {
case 'pow2':
calcRes.innerText = sq_pow(str,'pow','2');
calcMsg.innerText = `${str}\u00b2`;
break;
case 'pow3':
calcRes.innerText = sq_pow(str,'pow','3');
calcMsg.innerText = `${str}\u00b3=`;
break;
default:
calcRes.innerText = sq_pow(str,'sqrt');
calcMsg.innerText = calcRes.innerText.replace('Math.sqrt','√');
};
};
});
/* %按钮点击事件*/
percent.onclick = () => {
let num = calcRes.innerText;
calcRes.innerText = _eval(num/100);
calcMsg.innerText = num + '/100=';
};
/* 处理JS浮点数运算bug问题 */
let num2Fixed = (res) => {
let str = typeof(res) === 'number' ? res.toString() : res;
let ar = str.split('.');
/* 如果有小数点,需要排除科学计数小数点 */
if(ar.length <= 1 || str.indexOf('e') > -1) return res;
if(ar.length >= 14) str = str.slice(0,-1);
return parseFloat(str);
};
/* 幂&开方运算 */
let sq_pow = (str,operator,arg='') => {
operator = 'Math.' + operator;
let regex = /(\(+\))$|(\d+(\.\d+)?)$/g;
if(regex.test(str)) {
strRes = str.match(regex);
str = str.replace(regex, arg ? `${operator}(${strRes},${arg})` : `${operator}(${strRes})`);
}
return str;
};
</script> 本帖最后由 马黑黑 于 2024-1-2 23:50 编辑
代码:<style>
#ma { margin: 50px auto 0; padding: 8px; width: fit-content; max-width: 256px; box-sizing: border-box; height: fit-content; border-radius: 10px; background: linear-gradient(to right bottom, #666,tan); box-shadow: 2px 4px 8px black; }
#calcMsg, #calcRes { padding: 6px; color: #eee; font-size: 18px; word-break: break-all; }
#calcRes { text-align: right; color: #000; font-size: 20px; background: #eee; }
#calc { display: grid; place-items: center; grid-template-columns: repeat(4, 60px); grid-template-rows: repeat(5, 50px); }
.btn, .clear { width: 40px; height: 40px; border-radius: 50%; background: #aaa; box-shadow: inset 0 0 10px #000; font: normal 20px/40px sans-serif; text-align: center; user-select: none; cursor: pointer; }
.btn:hover, .clear:hover { background: #ccc; }
.num { color: darkred; font-weight: bold; }
</style>
<div id="ma">
<div id="calcMsg">计算器</div>
<div id="calcRes">0</div>
<div id="calc">
<div class="clear" id="clear">C</div>
<div class="btn" data-idx="(">(</div>
<div class="btn" data-idx=")">)</div>
<div class="clear" id="del">←</div>
<div class="btn num" data-idx="7">7</div>
<div class="btn num" data-idx="8">8</div>
<div class="btn num" data-idx="9">9</div>
<div class="btn" data-idx="/">÷</div>
<div class="btn num" data-idx="4">4</div>
<div class="btn num" data-idx="5">5</div>
<div class="btn num" data-idx="6">6</div>
<div class="btn" data-idx="*">×</div>
<div class="btn num" data-idx="1">1</div>
<div class="btn num" data-idx="2">2</div>
<div class="btn num" data-idx="3">3</div>
<div class="btn num" data-idx="-">-</div>
<div class="btn num" data-idx="0">0</div>
<div class="btn" data-idx=".">.</div>
<div class="btn" id="percent">%</div>
<div class="btn" data-idx="+">+</div>
<div class="clear" id="pow2">x²</div>
<div class="clear" id="pow3">x³</div>
<div class="clear" id="sqrt">√</div>
<div class="clear" id="equal" data-idx="=">=</div>
</div>
</div>
<script>
let btns = document.querySelectorAll('.btn'); /* 非id运算类按钮 */
/* 运算函数 */
let _eval = (fn) => new Function('return ' + fn)();
/* 处理运算结果 */
let calc = (str) => {
let res;
try {
res = _eval(str);
res = res == Infinity ? '∞' : num2Fixed(res);
} catch(err) {
res = '算式非法';
}
return res;
};
/* 非id类按钮点击事件 */
btns.forEach(item => {
item.onclick = () => {
if(calcRes.innerText === '0') {
if(item.dataset.idx === '0') {
calcRes.innerText = '0';
}else{
calcRes.innerText = isNaN(parseInt(item.dataset.idx)) ? '0' + item.dataset.idx : item.dataset.idx;
}
}else{
calcRes.innerText += item.dataset.idx;
}
calcMsg.innerText = calcRes.innerText;
}
});
/* C按钮点击事件 */
clear.onclick = () => {
calcMsg.innerText = '计算器';
calcRes.innerText = '0';
};
/* =按钮点击事件 */
equal.onclick = () => {
let result = calc(calcRes.innerText);
if(result === '算式非法') {
calcMsg.innerText = result;
}else{
calcRes.innerText = result;
}
if(calcMsg.innerText.lastIndexOf('=') < 0) calcMsg.innerText += '=';
};
/* ←按钮点击事件 */
del.onclick = () => {
calcRes.innerText = calcRes.innerText.slice(0,-1) || 0;
calcMsg.innerText = calcRes.innerText;
};
/* 开方&幂运算按钮点击事件 */
.forEach(elm => {
elm.onclick = () => {
let str = calcRes.innerText;
switch (elm.id) {
case 'pow2':
calcRes.innerText = sq_pow(str,'pow','2');
calcMsg.innerText = `${str}²`;
break;
case 'pow3':
calcRes.innerText = sq_pow(str,'pow','3');
calcMsg.innerText = `${str}³=`;
break;
default:
calcRes.innerText = sq_pow(str,'sqrt');
calcMsg.innerText = calcRes.innerText.replace('Math.sqrt','√');
};
};
});
/* %按钮点击事件*/
percent.onclick = () => {
let num = calcRes.innerText;
calcRes.innerText = _eval(num/100);
calcMsg.innerText = num + '/100=';
};
/* 处理JS浮点数运算bug问题 */
let num2Fixed = (res) => {
let str = typeof(res) === 'number' ? res.toString() : res;
let ar = str.split('.');
/* 如果有小数点,需要排除科学计数小数点 */
if(ar.length <= 1 || str.indexOf('e') > -1) return res;
if(ar.length >= 14) str = str.slice(0,-1);
return parseFloat(str);
};
/* 幂&开方运算 */
let sq_pow = (str,operator,arg='') => {
operator = 'Math.' + operator;
let regex = /(\(+\))$|(\d+(\.\d+)?)$/g;
if(regex.test(str)) {
strRes = str.match(regex);
str = str.replace(regex, arg ? `${operator}(${strRes},${arg})` : `${operator}(${strRes})`);
}
return str;
};
</script>
本帖最后由 马黑黑 于 2024-1-2 23:51 编辑
第二版相较于第一版,基于表达式(算式)的运算理念得以进一步落实。例如,四则运算中,将幂运算、平方运算纳入其中,只要算式合法,计算器均能操作。
同时解决一些已经发现的细节逻辑等若干问题。
肯定还会存在其他问题,后续再做修正。
{:5_116:}{:5_116:}{:5_116:}
评分和第一版一样,另外
期待第三版。
醉美水芙蓉 发表于 2024-1-3 07:12
黑黑老师厉害,发明了计算机!
{:4_170:}肿么可能?
计算器,练练手而已 起个网名好难 发表于 2024-1-3 08:19
评分和第一版一样,另外
未对评分机制导致的问题做考虑,现在的精力是处理具体的运算问题以及代码基于运算、操作逻辑等问题。感谢支持。 本帖最后由 起个网名好难 于 2024-1-3 10:30 编辑
马黑黑 发表于 2024-1-3 08:30
未对评分机制导致的问题做考虑,现在的精力是处理具体的运算问题以及代码基于运算、操作逻辑等问题。感谢 ...
用一对{}把代码包住评分就好了,不一定需要(function() { ... })(); 起个网名好难 发表于 2024-1-3 10:28
用一对{}把代码包住评分就好了,不一定需要(function() { ... })();
这个方法挺奇葩 黑黑把正则运算在这个帖子里做了完美的运用,很赞的钻研精神{:4_199:} 这么短的时间已经有了新版本的更新,厉害{:4_187:} 不但功能做了改进,连界面也变得更漂亮了呢{:4_187:} 红影 发表于 2024-1-3 12:30
不但功能做了改进,连界面也变得更漂亮了呢
还好还好 马黑黑 发表于 2024-1-3 12:25
这个方法挺奇葩
奇葩吗? let 的作用域是块,加上{}后就避免了let重复定义的问题。
从浏览器的调试信息中可以看到这个问题的原因
马黑黑 发表于 2024-1-3 12:31
还好还好
对这个不熟,只能在黑黑的帖子里欣赏一下了{:4_187:} 红影 发表于 2024-1-3 15:39
对这个不熟,只能在黑黑的帖子里欣赏一下了
计算器我倒是有一点点知道的,因为卖猪肉的关系{:4_170:} 红影 发表于 2024-1-3 12:28
黑黑把正则运算在这个帖子里做了完美的运用,很赞的钻研精神
不用正则也是可以的,会啰嗦一点 起个网名好难 发表于 2024-1-3 13:14
奇葩吗? let 的作用域是块,加上{}后就避免了let重复定义的问题。
从浏览器的调试信息中可以看到这个问 ...
嗯。我只是对 {} 的语义问题不解 马黑黑 发表于 2024-1-3 16:31
嗯。我只是对 {} 的语义问题不解
就是定义个代码块,不知是否确切。
起个网名好难 发表于 2024-1-3 18:43
就是定义个代码块,不知是否确切。
应该是。只是,关于 {} 的官网文档,都没这个用法的。