基于JavaScript實(shí)現(xiàn)字符串計(jì)算器的實(shí)戰(zhàn)指南
用代碼實(shí)現(xiàn)一個(gè)計(jì)算器,是面試和刷題里很常見(jiàn)的一類(lèi)題。很多人一上來(lái)就想寫(xiě)「完整版」,容易在符號(hào)優(yōu)先級(jí)、括號(hào)、多位數(shù)上一起踩坑。更好的方式是:先解決最小子問(wèn)題,再一層層加能力。
這篇文章會(huì)按下面這條路線(xiàn),帶你從零搭出一個(gè)支持 + - * / 和括號(hào)的表達(dá)式計(jì)算器,并在最后做一輪優(yōu)化。
- 字符串轉(zhuǎn)整數(shù):先把「數(shù)字字符串」正確轉(zhuǎn)成數(shù)字
- 只做加減:引入運(yùn)算符和棧,處理
+、- - 加入乘除:在原有框架上加上
*、/與優(yōu)先級(jí) - 支持括號(hào):用遞歸或區(qū)間,處理嵌套括號(hào)
- 優(yōu)化:避免爆棧、少做拷貝、寫(xiě)法更穩(wěn)
每一步都會(huì)在前一步可運(yùn)行代碼的基礎(chǔ)上做增量修改,方便你跟著寫(xiě)、跟著測(cè)。
第一步:字符串轉(zhuǎn)整數(shù)
表達(dá)式是由「數(shù)字」和「運(yùn)算符」組成的,而數(shù)字在字符串里可能是多位數(shù),比如 "123"。所以首先要會(huì):把一段連續(xù)的數(shù)字字符轉(zhuǎn)成一個(gè)整數(shù)。
核心就一句話(huà):每多一位,相當(dāng)于原來(lái)的數(shù)左移一位(×10),再加上當(dāng)前這一位。
"1"→ 先得到1"12"→ 原來(lái)的1變成十位:1*10+2 = 12"123"→ 原來(lái)的12再左移一位:12*10+3 = 123
用代碼寫(xiě)出來(lái)就是這樣(只考慮正整數(shù)、無(wú)空格無(wú)符號(hào))。用 while 把連續(xù)數(shù)字一次性讀完,后面各步也都用這一套邏輯,更統(tǒng)一:
/**
* 將純數(shù)字字符串轉(zhuǎn)為整數(shù)(如 "123" → 123)
* @param {string} str - 僅包含數(shù)字字符的字符串
* @returns {number} 轉(zhuǎn)換后的整數(shù)
*/
function stringToInt(str) {
let num = 0; // 累加結(jié)果,初始為 0(乘法的單位元,不影響首次 num*10+cur)
let i = 0; // 當(dāng)前掃描到的下標(biāo)
// 只要未越界且當(dāng)前字符是數(shù)字,就繼續(xù)讀(多位數(shù)一次性讀完)
while (i < str.length && /[0-9]/.test(str[i])) {
num = num * 10 + (str[i] - '0'); // 左移一位(×10)+ 當(dāng)前位數(shù)字;'5'-'0' → 5
i++;
}
return num;
}
這里用 str[i] - '0' 把字符轉(zhuǎn)成數(shù)字,比 parseInt 更直接,也避免一些隱式轉(zhuǎn)換問(wèn)題。
建議:自己寫(xiě)一遍,用 "0"、"9"、"123"、"1000" 測(cè)幾組,確保多位數(shù)和邊界都對(duì)。這是后面所有步驟的「數(shù)字解析」基礎(chǔ)。
第二步:只做加減法
假設(shè)輸入只有 +、-、數(shù)字和空格,例如:"1 - 12 + 3"。目標(biāo)是:一次遍歷,得到結(jié)果。
一個(gè)很好用的思路是:把減法變成「加負(fù)數(shù)」,這樣最后只需要做「一堆數(shù)求和」。
- 給第一個(gè)數(shù)字前面補(bǔ)一個(gè)默認(rèn)的
+,整體看成:+1 -12 +3。 - 把一個(gè)符號(hào) + 一個(gè)數(shù)字綁在一起,得到幾組:
(+1)、(-12)、(+3)。 - 把這些數(shù)全部加在一起:
1 + (-12) + 3 = -8。
實(shí)現(xiàn)時(shí):
- 用一個(gè)變量
sign表示「當(dāng)前正在拼的這個(gè)數(shù)字前面是+還是-」,初始為'+'。 - 遇到數(shù)字就按「第一步」的方式拼成多位數(shù),拼在
curNum里,然后根據(jù)數(shù)字前面的sign,進(jìn)棧 - 遍歷結(jié)束后,棧里全是「帶符號(hào)的數(shù)」,求和即可。
/**
* 只支持 +、-、數(shù)字、空格的表達(dá)式(減法轉(zhuǎn)為加負(fù)數(shù),最后統(tǒng)一求和)
* @param {string} s - 表達(dá)式字符串,如 "1 - 12 + 3"
* @returns {number} 計(jì)算結(jié)果
*/
function calculateAddSub(s) {
const stack = []; // 存「帶符號(hào)的加數(shù)」,最后全部相加即可
let sign = '+'; // 當(dāng)前數(shù)字前的符號(hào),第一個(gè)數(shù)字前默認(rèn)為 +
const n = s.length;
for (let i = 0; i < n; i++) {
const c = s[i];
if (c === ' ') continue; // 空格跳過(guò),避免被誤判為數(shù)字
// 遇到數(shù)字:用 while 一次性讀完多位數(shù)
if (/[0-9]/.test(c)) {
let num = 0;
while (i < n && /[0-9]/.test(s[i])) {
num = num * 10 + (s[i] - '0');
i++;
}
stack.push(sign === '+' ? num : -num); // 按當(dāng)前符號(hào)入棧(減法即存負(fù)數(shù))
i--; // while 結(jié)束時(shí) i 已指向非數(shù)字,for 會(huì)再 i++,故回退一步避免跳過(guò)
continue;
}
// 遇到 + 或 -:只更新符號(hào),下一個(gè)數(shù)字將使用該符號(hào)入棧
if (c === '+' || c === '-') {
sign = c;
}
}
// 棧內(nèi)全是帶符號(hào)的加數(shù),直接求和
let sum = 0;
while (stack.length) sum += stack.pop();
return sum;
}
要點(diǎn):
- 空格直接
continue,避免被誤當(dāng)數(shù)字。 - 遇到數(shù)字就用
while一次性讀完,入棧后記得i--,因?yàn)?for會(huì)再i++,否則會(huì)跳過(guò)一個(gè)字符。 - 遇到
+、-只更新sign,數(shù)字在讀完時(shí)按當(dāng)前sign入棧;棧里存的已經(jīng)是「帶符號(hào)的加數(shù)」,最后只做加法。
到這里,你已經(jīng)有了「數(shù)字解析 + 運(yùn)算符 + ?!沟目蚣埽乱徊街皇窃谶@個(gè)框架上擴(kuò)展運(yùn)算符種類(lèi)和優(yōu)先級(jí)。
第三步:加入乘除
在「加減 + ?!沟幕A(chǔ)上,再支持 *、/,并且滿(mǎn)足:乘除優(yōu)先于加減。
做法可以這樣理解:
- 加減:像第二步一樣,把數(shù)字帶上符號(hào)壓棧(減法即壓負(fù)值),最后統(tǒng)一求和。
- 乘除:需要「立刻算掉」,因?yàn)槌顺膬?yōu)先級(jí)高。做法是:遇到
*或/時(shí),用當(dāng)前剛拼好的數(shù)和棧頂?shù)臄?shù)做運(yùn)算,把結(jié)果再壓回棧頂。這樣棧里存的仍然是「已經(jīng)按優(yōu)先級(jí)算好的、待求和的項(xiàng)」。
所以我們需要一個(gè)「數(shù)字前的運(yùn)算符」signBeforeNum:
- 遇到
+、-:把當(dāng)前數(shù)字按正/負(fù)入棧,只改signBeforeNum,不入棧時(shí)做運(yùn)算。 - 遇到
*、/:先按signBeforeNum把當(dāng)前數(shù)字和棧頂結(jié)合(若是*//就彈出棧頂算完再壓回),再更新signBeforeNum。
為了代碼清晰,可以把「根據(jù)符號(hào)把 num 入棧」抽成一個(gè)函數(shù):
/**
* 根據(jù)「數(shù)字前的運(yùn)算符」把 num 入棧(加減存帶符號(hào)數(shù),乘除與棧頂算完再入棧)
* @param {number[]} stack - 數(shù)字棧
* @param {string} signBeforeNum - 該數(shù)字前的運(yùn)算符(+、-、*、/)
* @param {number} num - 剛讀完的數(shù)字
*/
function addNumToStack(stack, signBeforeNum, num) {
switch (signBeforeNum) {
case '+':
stack.push(num); // 加:直接入棧
break;
case '-':
stack.push(-num); // 減:轉(zhuǎn)成加負(fù)數(shù)入棧
break;
case '*':
stack.push(stack.pop() * num); // 乘:彈出棧頂 × 當(dāng)前數(shù),結(jié)果入棧(乘除優(yōu)先)
break;
case '/':
const top = stack.pop();
stack.push(Math.trunc(top / num)); // 除:向零取整,如 -5/2 → -2
break;
}
}
/**
* 支持 +、-、*、/、數(shù)字、空格,乘除優(yōu)先于加減
* @param {string} s - 表達(dá)式,如 "10 - 2*3 + 8/2"
* @returns {number} 計(jì)算結(jié)果
*/
function calculateMulDiv(s) {
const stack = []; // 存待累加的項(xiàng)(加減已轉(zhuǎn)符號(hào),乘除已算完)
let signBeforeNum = '+'; // 「下一個(gè)數(shù)字」前的運(yùn)算符,第一個(gè)數(shù)字前默認(rèn)為 +
const n = s.length;
for (let i = 0; i < n; i++) {
const c = s[i];
if (c === ' ') continue;
// 遇到數(shù)字:while 讀完多位數(shù),再按 signBeforeNum 入棧
if (/[0-9]/.test(c)) {
let num = 0;
while (i < n && /[0-9]/.test(s[i])) {
num = num * 10 + (s[i] - '0');
i++;
}
addNumToStack(stack, signBeforeNum, num);
i--;
continue;
}
// 遇到運(yùn)算符:只更新「下一個(gè)數(shù)字前的符號(hào)」,數(shù)字入棧在讀完數(shù)字時(shí)由 addNumToStack 完成
if (['+', '-', '*', '/'].includes(c)) {
signBeforeNum = c;
}
}
return stack.reduce((a, b) => a + b, 0);
}
注意:
- 除法用
Math.trunc,這樣-5/2 === -2,符合「向零取整」的慣例。 - 數(shù)字用
while一次性讀完,入棧后i--,與第二步一致。
到這一步,不帶括號(hào)的四則運(yùn)算已經(jīng)完成,邏輯也是后面「帶括號(hào)版」的基礎(chǔ)。
第四步:支持括號(hào)
括號(hào)的語(yǔ)義是:括號(hào)內(nèi)的表達(dá)式先算,結(jié)果當(dāng)作一個(gè)數(shù)。所以可以很自然想到:
- 遇到
(:找到與之匹配的),對(duì)中間這一段遞歸計(jì)算,得到的結(jié)果當(dāng)作「一個(gè)數(shù)字」,再按當(dāng)前的signBeforeNum入棧。 - 遇到
):不需要在本層處理,因?yàn)槭怯伞钙ヅ涞?(那一層」通過(guò)遞歸邊界自然結(jié)束的。
因此需要解決兩件事:
- 括號(hào)匹配:給定一個(gè)
(的位置,找到對(duì)應(yīng)的)。嵌套括號(hào)用棧即可:從左到右掃,(壓棧,)彈棧,彈出來(lái)的就是匹配的左括號(hào),記錄下右括號(hào)位置即可得到「左 → 右」的映射。 - 遞歸范圍:遇到
(時(shí),用映射找到配對(duì)的),對(duì)子串s.slice(left+1, right)遞歸計(jì)算(不包含括號(hào)本身),結(jié)果當(dāng)作一個(gè)數(shù)入棧,然后把下標(biāo)i直接跳到),避免重復(fù)處理。
下面先給出未優(yōu)化版:每次進(jìn)入 calculate(s) 都會(huì)對(duì)當(dāng)前 s 算一遍 getBracketMap(s),遞歸時(shí)用 s.slice(i+1, right) 復(fù)制出一段子串再遞歸。邏輯清晰,但長(zhǎng)表達(dá)式、深括號(hào)時(shí)會(huì)有重復(fù)計(jì)算和大量拷貝,第五步再優(yōu)化。
先寫(xiě)括號(hào)匹配(只掃一遍字符串):
/**
* 建立括號(hào)配對(duì):左括號(hào)下標(biāo) → 對(duì)應(yīng)的右括號(hào)下標(biāo)(嵌套括號(hào)用棧就近匹配)
* @param {string} s - 表達(dá)式字符串
* @returns {Map<number, number>} 左括號(hào)索引 → 右括號(hào)索引
*/
function getBracketMap(s) {
const map = new Map();
const stack = []; // 存左括號(hào)的下標(biāo),遇到 ) 時(shí)彈棧即得到與之匹配的 (
for (let i = 0; i < s.length; i++) {
if (s[i] === '(') {
stack.push(i); // 左括號(hào):記錄其下標(biāo),等待匹配
}
if (s[i] === ')') {
const left = stack.pop(); // 彈出最近的左括號(hào)下標(biāo),即與當(dāng)前 ) 配對(duì)
map.set(left, i); // 記錄配對(duì)關(guān)系
}
}
return map;
}
然后在主邏輯里加上對(duì) ( 的分支(遞歸時(shí)傳入子串,即未優(yōu)化版):
/**
* 表達(dá)式計(jì)算函數(shù)(支持 +-*\/、數(shù)字、空格、嵌套括號(hào),遵循 括號(hào)>乘除>加減 優(yōu)先級(jí))
* @param {string} s - 輸入表達(dá)式,示例:"1+2+1*3+(3*6+12*(3+3))-5"
* @returns {number} - 計(jì)算結(jié)果
*
* 核心設(shè)計(jì)思路:
* 1. 棧的作用:存儲(chǔ)待累加的數(shù)字(根據(jù)符號(hào)決定數(shù)字進(jìn)棧的值,加減轉(zhuǎn)符號(hào)存儲(chǔ),乘除優(yōu)先計(jì)算后存儲(chǔ),括號(hào)結(jié)果作為單個(gè)數(shù)字存儲(chǔ))
* 2. 符號(hào)規(guī)則:signBeforeNum 記錄「當(dāng)前數(shù)字的前一個(gè)運(yùn)算符」,決定數(shù)字如何入棧
* 3. 字符處理優(yōu)先級(jí):空格跳過(guò) → 數(shù)字拼接 → 左括號(hào)遞歸 → 運(yùn)算符更新符號(hào)
*/
function calculate(s) {
const stack = []; // 存儲(chǔ)待累加的數(shù)字棧(核心數(shù)據(jù)結(jié)構(gòu))
const bracketMap = getBracketMap(s); // 預(yù)生成「左括號(hào)索引→右括號(hào)索引」映射,解決嵌套括號(hào)匹配
let signBeforeNum = '+'; // 【易錯(cuò)點(diǎn)1】初始值必須為+,對(duì)應(yīng)第一個(gè)數(shù)字前默認(rèn)是加號(hào)
// 遍歷表達(dá)式的每個(gè)字符
for (let i = 0; i < s.length; i++) {
let curChar = s[i];
// 1. 跳過(guò)空格(唯一需要主動(dòng)跳過(guò)的特殊字符)
if (curChar === ' ') continue;
// 2. 處理連續(xù)數(shù)字拼接(核心邏輯:多位數(shù)拆解)
if (/[0-9]/.test(curChar)) {
let num = 0;
// 循環(huán)拼接所有連續(xù)數(shù)字,直到遇到非數(shù)字字符
while (i < s.length && /[0-9]/.test(s[i])) {
// 【易錯(cuò)點(diǎn)2】必須加i < s.length,避免越界
// 【易錯(cuò)點(diǎn)3】必須用s[i]而非curChar,curChar是循環(huán)外的初始值,不會(huì)隨i更新
const singleNum = s[i] - '0';
num = num * 10 + singleNum; // 位權(quán)原理拼接多位數(shù)(如1→12→123)
i++; // 移動(dòng)到下一個(gè)字符
}
// 數(shù)字拼接完成,按符號(hào)入棧
addNumToStack(stack, num, signBeforeNum);
// 【易錯(cuò)點(diǎn)4】核心細(xì)節(jié):循環(huán)后i指向非數(shù)字,外層i++會(huì)跳過(guò)運(yùn)算符,必須回退一步
i--;
continue; // 跳過(guò)后續(xù)邏輯,避免數(shù)字被誤判為運(yùn)算符
}
// 3. 處理左括號(hào)(遞歸+索引跳轉(zhuǎn),無(wú)需處理右括號(hào))
if (curChar === '(') {
const rightBracketIdx = bracketMap.get(i); // 獲取匹配的右括號(hào)索引
// 【易錯(cuò)點(diǎn)5】遞歸截取范圍是[i+1, rightBracketIdx),不包含右括號(hào)
const bracketRes = calculate(s.slice(i + 1, rightBracketIdx));
// 括號(hào)結(jié)果作為單個(gè)數(shù)字,按當(dāng)前符號(hào)入棧
addNumToStack(stack, bracketRes, signBeforeNum);
// 【易錯(cuò)點(diǎn)6】跳到右括號(hào)索引,外層i++會(huì)自然跳過(guò)右括號(hào),無(wú)需額外處理)
i = rightBracketIdx;
continue; // 本次循環(huán)結(jié)束,避免后續(xù)邏輯處理右括號(hào)
}
// 4. 處理運(yùn)算符(僅更新符號(hào),數(shù)字入棧邏輯在數(shù)字拼接時(shí)觸發(fā))
if (['+', '-', '*', '/'].includes(curChar)) {
// 【易錯(cuò)點(diǎn)7】符號(hào)僅記錄,不立即計(jì)算,作用于下一個(gè)數(shù)字
signBeforeNum = curChar;
}
}
// 5. 棧內(nèi)所有數(shù)字累加(加減已轉(zhuǎn)符號(hào),乘除/括號(hào)已計(jì)算,直接求和)
let sum = 0;
while (stack.length) {
sum += stack.pop();
}
return sum;
}
注意:
- 遞歸區(qū)間用
s.slice(i+1, right),不包含左右括號(hào);每一層遞歸收到的都是「當(dāng)前層」的字符串,所以本層算出的bracketMap就是當(dāng)前s的映射。 i = right之后,外層for會(huì)執(zhí)行i++,所以不會(huì)重復(fù)處理)。- 未優(yōu)化點(diǎn):每次遞歸都復(fù)制一次子串,且每次進(jìn)入
calculate(s)都會(huì)執(zhí)行一次getBracketMap(s),表達(dá)式長(zhǎng)、括號(hào)深時(shí)容易爆棧或變慢。下一步在第五步里改成「只算一次 bracketMap、只傳區(qū)間不復(fù)制」。(主要是跑leetCode224題目的時(shí)候,用例沒(méi)過(guò))
第五步:優(yōu)化
在第四步未優(yōu)化版的基礎(chǔ)上做兩處改動(dòng):不復(fù)制字符串、只算一次括號(hào)映射。
1. 不拷貝字符串,只傳區(qū)間
遞歸時(shí)不再用 calculate(s.slice(i + 1, right)),而是始終在原串 s 上操作,用內(nèi)部函數(shù) calc(low, high) 表示「只處理 s[low..high] 這一段」。遞歸時(shí)調(diào)用 calc(i + 1, right - 1),不產(chǎn)生任何子串拷貝。
2. 只算一次括號(hào)映射
getBracketMap(s) 只依賴(lài)整串 s,和遞歸層數(shù)無(wú)關(guān)。在最外層入口算一次,得到整串的「左括號(hào)下標(biāo) → 右括號(hào)下標(biāo)」映射,然后在整個(gè)遞歸過(guò)程中通過(guò)閉包共用這一個(gè) bracketMap。這樣每一層不再重新掃一遍當(dāng)前子串,避免重復(fù)計(jì)算。
優(yōu)化后的主邏輯示意(與文末完整實(shí)現(xiàn)一致):入口處 const bracketMap = getBracketMap(s) 只執(zhí)行一次,然后 return calc(0, s.length - 1);內(nèi)部 calc(low, high) 遇到 ( 時(shí)用 bracketMap.get(i) 取到匹配的右括號(hào)下標(biāo)(因?yàn)?i 始終是原串下標(biāo)),再遞歸 calc(i + 1, right - 1),并令 i = right 跳過(guò)整段括號(hào)。
3. 數(shù)字拼接時(shí)的小細(xì)節(jié)
- 用
while把連續(xù)數(shù)字一次性拼完,避免用curChar而忘記用s[i]導(dǎo)致死循環(huán)或錯(cuò)位;拼完后記得i--,因?yàn)?for會(huì)再i++,否則會(huì)跳過(guò)一個(gè)字符。 - 數(shù)字判斷用
/[0-9]/.test(s[i]),循環(huán)條件里務(wù)必寫(xiě)i < s.length,防止越界。
4. 除法取整
堅(jiān)持用 Math.trunc(top / num),這樣負(fù)數(shù)的除法行為一致(向零取整),和常見(jiàn)數(shù)學(xué)期望一致。
/**
* 建立括號(hào)配對(duì):左括號(hào)下標(biāo) → 右括號(hào)下標(biāo)(僅掃一遍,嵌套用棧匹配)
*/
function getBracketMap(s) {
const map = new Map();
const stack = []; // 存左括號(hào)下標(biāo)
for (let i = 0; i < s.length; i++) {
if (s[i] === '(') stack.push(i);
if (s[i] === ')') map.set(stack.pop(), i);
}
return map;
}
/**
* 按「數(shù)字前的運(yùn)算符」將 num 入棧:加減存帶符號(hào)數(shù),乘除與棧頂算完再入棧
*/
function addNumToStack(stack, signBeforeNum, num) {
switch (signBeforeNum) {
case '+':
stack.push(num);
break;
case '-':
stack.push(-num);
break;
case '*':
stack.push(stack.pop() * num);
break;
case '/':
stack.push(Math.trunc(stack.pop() / num));
break;
}
}
/**
* 表達(dá)式計(jì)算(優(yōu)化版:不復(fù)制 s,只傳區(qū)間;bracketMap 只算一次)
* @param {string} s - 整段表達(dá)式
* @returns {number} 計(jì)算結(jié)果
*/
function calculate(s) {
const bracketMap = getBracketMap(s); // 整串只算一次,遞歸過(guò)程中通過(guò)閉包共用
return calc(0, s.length - 1);
/** 只處理 s[low..high] 這一段,不復(fù)制字符串 */
function calc(low, high) {
const stack = [];
let signBeforeNum = '+';
for (let i = low; i <= high; i++) {
const c = s[i];
if (c === ' ') continue;
// 數(shù)字:while 讀完多位數(shù)(注意邊界 i <= high),入棧后 i-- 避免 for 多跳一格
if (/[0-9]/.test(c)) {
let num = 0;
while (i <= high && /[0-9]/.test(s[i])) {
num = num * 10 + (s[i] - '0');
i++;
}
addNumToStack(stack, signBeforeNum, num);
i--;
continue;
}
// 左括號(hào):用預(yù)算好的 bracketMap 取匹配的右括號(hào),遞歸區(qū)間 [i+1, right-1],結(jié)果入棧后跳過(guò)整段
if (c === '(') {
const right = bracketMap.get(i);
const sub = calc(i + 1, right - 1);
addNumToStack(stack, signBeforeNum, sub);
i = right;
continue;
}
// 運(yùn)算符:更新「下一個(gè)數(shù)字前的符號(hào)」
if (['+', '-', '*', '/'].includes(c)) {
signBeforeNum = c;
}
}
return stack.reduce((a, b) => a + b, 0);
}
}
測(cè)試示例:
calculate('1 + 1')→ 2calculate(' 2-1 + 2 ')→ 3calculate('10 - 2*3 + 8/2')→ 8calculate('(1+(4+5+2)-3)+(6+8)')→ 23calculate('3*(2-6/(3-7))')→ 12
小結(jié)
整體路線(xiàn)可以概括成:
| 步驟 | 能力 | 核心點(diǎn) |
|---|---|---|
| 1 | 字符串轉(zhuǎn)整數(shù) | while 讀連續(xù)數(shù)字,num = num * 10 + (c - '0'),多位數(shù)左移再累加 |
| 2 | 加減 | 同上 while 讀數(shù)字,讀完按 sign 入棧;減法當(dāng)加負(fù)數(shù),最后求和 |
| 3 | 乘除 | 同上 while 讀數(shù)字,讀完用 addNumToStack;乘除與棧頂算完再入棧 |
| 4 | 括號(hào)(未優(yōu)化) | 同上 while 讀數(shù)字;每層 getBracketMap(s),遞歸用 s.slice 傳子串 |
| 5 | 優(yōu)化 | 不 slice、只傳區(qū)間 calc(low,high);只算一次 bracketMap;注意 i-- 與越界 |
按這個(gè)順序?qū)崿F(xiàn),每一步都能單獨(dú)測(cè),最后再接到一起,就是一個(gè)清晰、可維護(hù)的字符串計(jì)算器實(shí)現(xiàn)。如果你愿意,還可以在此基礎(chǔ)上再擴(kuò)展:負(fù)號(hào)、空格預(yù)處理、更多運(yùn)算符等,思路都是在這一套「棧 + 符號(hào) + 區(qū)間」上做延伸。
如果你按「字符串轉(zhuǎn)整數(shù) → 加減 → 乘除 → 括號(hào) → 優(yōu)化」的順序自己實(shí)現(xiàn)一遍,再和這份代碼對(duì)照,會(huì)更容易抓住每一步的意圖和易錯(cuò)點(diǎn)。
到此這篇關(guān)于基于JavaScript實(shí)現(xiàn)字符串計(jì)算器的實(shí)戰(zhàn)指南的文章就介紹到這了,更多相關(guān)JavaScript字符串計(jì)算器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript間隔刷新的簡(jiǎn)單實(shí)例
這篇文章介紹了javascript間隔刷新的簡(jiǎn)單實(shí)例,有需要的朋友可以參考一下2013-11-11
解決window.opener=null;window.close(),只支持IE6不支持IE7,IE8的問(wèn)題
本篇文章主要是對(duì)window.opener=null;window.close(),只支持IE6不支持IE7,IE8的解決方法進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-01-01
JavaScript實(shí)現(xiàn)彩虹文字效果的方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)彩虹文字效果的方法,涉及javascript操作文字樣式的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
JS利用正則表達(dá)式實(shí)現(xiàn)簡(jiǎn)單的密碼強(qiáng)弱判斷實(shí)例
這篇文章主要給大家介紹了關(guān)于JS利用正則表達(dá)式實(shí)現(xiàn)簡(jiǎn)單的密碼強(qiáng)弱判斷的相關(guān)資料,實(shí)現(xiàn)后的效果非常簡(jiǎn)單,但也挺實(shí)用的,文中給出了詳細(xì)的示例代碼供大家參考學(xué)習(xí),需要的朋友們下面來(lái)一起看看吧。2017-06-06
javascript模擬鼠標(biāo)點(diǎn)擊事件原理和實(shí)現(xiàn)方法
本文詳細(xì)介紹了JS模擬鼠標(biāo)點(diǎn)擊事件的原理以及應(yīng)用場(chǎng)景,并提供了模擬鼠標(biāo)左鍵點(diǎn)擊事件、右鍵點(diǎn)擊事件、滾輪事件和移動(dòng)事件的代碼實(shí)現(xiàn),了解JS模擬鼠標(biāo)點(diǎn)擊事件的原理和實(shí)現(xiàn)方法對(duì)于開(kāi)發(fā)人員非常重要,這對(duì)于許多面向用戶(hù)的web應(yīng)用程序的開(kāi)發(fā)和測(cè)試都具有很重要的意義2023-09-09

