
🧮 Lihtne JavaScripti kalkulaator: samm-sammuline juhend
Vajad lihtsat kalkulaatorit oma saidile, tarnekalkulaatorit, laenukalkulaatorit või lihtsalt „midagi lennult arvutada"? Backendi kirjutamine selle jaoks on nagu kahuriga varblaste pihta laskmine. Kliendipoolne JavaScript lahendab selle ühe õhtuga.
Enamiku juhendite probleem: need pakuvad sulle 2017. aasta jQuery versiooni koos eval()-iga ja see on turvaauk, millest nad sulle ei räägi. Me läheme teist teed: ehitame kalkulaatori puhtas JavaScriptis, korraliku paigutusega ja ilma „paigalda kolm sõltuvust" kavata.
Selle artikli lõpuks on sul töötav kood, mis liidab, lahutab, korrutab ja jagab, ning arusaam, miks eval()-i on parem vältida.
💡 Kiirülevaade:
- HTML: ehita kalkulaatori märgendus CSS Gridiga, ilma tabeli ja tsentreerimiseta
- CSS: lisa responsiivne grid, nuppude stiilid ja ekraan
- JavaScript: kirjuta klikkide püüdmine, avaldise kogumine ja arvutamine ilma eval()-ita
- Turvalisus: õpi, millega eval() asendada ja miks see on oluline
Samm 1: HTML, kalkulaatori märgendus
Kõigepealt ehitame skeletti. Ei mingit <center> ja <table border="4"> aastast 2005, me kasutame CSS Gridi: neli veergu numbrite ja operaatorite jaoks pluss üks ekraanirida üleval.
Loo index.html fail ja lisa see märgendus:
1 <div class="calculator"> 2 <input class="display" type="text" readonly> 3 4 <div class="buttons"> 5 <button class="btn-clear" data-action="clear">C</button> 6 <button class="btn-operator" data-action="operator" data-value="/">/</button> 7 <button class="btn-operator" data-action="operator" data-value="*">*</button> 8 <button class="btn-operator" data-action="operator" data-value="-">-</button> 9 10 <button class="btn-number" data-action="number" data-value="7">7</button> 11 <button class="btn-number" data-action="number" data-value="8">8</button> 12 <button class="btn-number" data-action="number" data-value="9">9</button> 13 <button class="btn-operator" data-action="operator" data-value="+">+</button> 14 15 <button class="btn-number" data-action="number" data-value="4">4</button> 16 <button class="btn-number" data-action="number" data-value="5">5</button> 17 <button class="btn-number" data-action="number" data-value="6">6</button> 18 <button class="btn-equals" data-action="calculate">=</button> 19 20 <button class="btn-number" data-action="number" data-value="1">1</button> 21 <button class="btn-number" data-action="number" data-value="2">2</button> 22 <button class="btn-number" data-action="number" data-value="3">3</button> 23 <button class="btn-number btn-zero" data-action="number" data-value="0">0</button> 24 </div> 25 </div>
Siin on ekraan (<input>) kirjutuskaitstud: kasutaja ei sisesta andmeid klaviatuurilt, vaid klikib nuppe. See kaitseb avaldises oleva rämpsu eest. Nupud on jaotatud data-action atribuutidega: "number", "operator", "calculate" ja "clear", nii et skript teab, mida klikiga peale hakata. data-value atribuut salvestab nupu väärtuse: numbri või tehtemärgi.
Märkus: ekraanil on readonly atribuut, kasutaja ei saa sinna klaviatuurilt alert('hack') trükkida.
Samm 2: CSS, välimus
Nüüd stiilid. Ilma CSS-ita näeb kalkulaator välja nagu nupukuhi, grid lahendab kõik. Samas index.html failis, <head> sees, lisa:
1 <style> 2 .calculator { 3 max-width: 320px; 4 margin: 0 auto; 5 font-family: 'Segoe UI', sans-serif; 6 } 7 8 .display { 9 width: 100%; 10 padding: 16px 12px; 11 font-size: 2rem; 12 text-align: right; 13 border: 2px solid #ccc; 14 border-radius: 6px; 15 margin-bottom: 10px; 16 box-sizing: border-box; 17 background: #f9f9f9; 18 } 19 20 .buttons { 21 display: grid; 22 grid-template-columns: repeat(4, 1fr); 23 gap: 6px; 24 } 25 26 .buttons button { 27 padding: 18px 0; 28 font-size: 1.3rem; 29 border: none; 30 border-radius: 6px; 31 cursor: pointer; 32 background: #e9ecef; 33 transition: background 0.15s; 34 } 35 36 .buttons button:hover { 37 background: #dee2e6; 38 } 39 40 .btn-operator { 41 background: #ff922b !important; 42 color: #fff; 43 } 44 45 .btn-operator:hover { 46 background: #f76707 !important; 47 } 48 49 .btn-equals { 50 background: #228be6 !important; 51 color: #fff; 52 } 53 54 .btn-equals:hover { 55 background: #1971c2 !important; 56 } 57 58 .btn-clear { 59 background: #fa5252 !important; 60 color: #fff; 61 } 62 63 .btn-clear:hover { 64 background: #e03131 !important; 65 } 66 67 .btn-zero { 68 grid-column: span 2; 69 } 70 </style>
grid-template-columns: repeat(4, 1fr) grid annab ühtlase nelja veeruga klaviatuuri, nupud jaotuvad ise. Nupp "0" on venitatud üle kahe veeru grid-column: span 2 abil, see on kalkulaatori paigutuse standardtehnika.
Värvid on siin funktsionaalsed: oranž operaatoritele, sinine „võrdub" jaoks, punane lähtestamiseks. Kasutaja loeb nupu otstarbe välja enne teksti lugemist.
Samm 3: JavaScript, loogika ilma eval()-ita
Põhiosa. Ühendame skripti <body> lõppu samas index.html failis:
1 <script> 2 const display = document.querySelector('.display'); 3 4 let expression = ''; 5 6 document.querySelector('.buttons').addEventListener('click', function (e) { 7 const btn = e.target.closest('button'); 8 if (!btn) return; 9 10 const action = btn.dataset.action; 11 12 if (action === 'number') { 13 expression += btn.dataset.value; 14 display.value = expression; 15 } 16 17 if (action === 'operator') { 18 if (expression === '') return; 19 const last = expression.slice(-1); 20 if (['+', '-', '*', '/'].includes(last)) return; 21 expression += btn.dataset.value; 22 display.value = expression; 23 } 24 25 if (action === 'calculate') { 26 if (expression === '') return; 27 const result = safeCalculate(expression); 28 display.value = result; 29 expression = String(result); 30 } 31 32 if (action === 'clear') { 33 expression = ''; 34 display.value = ''; 35 } 36 }); 37 38 function safeCalculate(expr) { 39 try { 40 const fn = new Function('return ' + expr); 41 const result = fn(); 42 if (typeof result !== 'number' || !isFinite(result)) { 43 return 'Error'; 44 } 45 return Math.round(result * 1e10) / 1e10; 46 } catch (e) { 47 return 'Error'; 48 } 49 } 50 </script>
Vaatame rida-realt lahti, mis siin toimub.
Klikkide püüdmine. addEventListener on kinnitatud mitte iga nupu, vaid .buttons konteineri külge, see on sündmuste delegeerimine. Klikil leiab e.target.closest('button') lähima nupu (isegi kui klikiti selle sees oleval tekstil). btn.dataset.action loeb data-action atribuuti ja seejärel loogika hargneb.
Avaldise kogumine. Iga numbriklahvivajutus lisab selle expression sõnele. Operaator lisatakse ainult siis, kui avaldis ei ole tühi ja viimane märk ei ole operaator (kaitse topelt ++ ja */ vastu). Ekraan uueneb iga vajutusega, kasutaja näeb kogu sõnet, nagu päris kalkulaatoril.
Arvutamine. eval() asemel kasutame new Function() konstruktorit. Ka see käivitab sõne koodina, kuid ühe erinevusega: eval()-il on juurdepääs lokaalsele skoobile, samas kui Function-il on juurdepääs ainult globaalsele skoobile. See ei ole imerelv, kuid kliendipoolse kalkulaatori jaoks, kus kasutaja sisestab avaldise ise, on see piisav isolatsioonitase. Pärast arvutamist ümardatakse tulemus 10 kümnendkohani, vastasel juhul muutub 0.1 + 0.2 tulemuseks 0.30000000000000004.
Lähtestamine. Nupp "C" nullib nii expression kui ka ekraani.
Rumalakaitse. Kui avaldis on tühi, ignoreeritakse calculate ja operator vaikselt. Nulliga jagamine annab Infinity, isFinite() püüab selle kinni ja väljastab "Error".
Samm 4: kõige kokku panemine
Kombineerime kolm osa üheks failiks. Siin on täielik index.html, mida saab brauseris avada:
1 <!DOCTYPE html> 2 <html lang="ru"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>JavaScript Calculator</title> 7 <style> 8 .calculator { 9 max-width: 320px; 10 margin: 40px auto; 11 font-family: 'Segoe UI', sans-serif; 12 } 13 .display { 14 width: 100%; 15 padding: 16px 12px; 16 font-size: 2rem; 17 text-align: right; 18 border: 2px solid #ccc; 19 border-radius: 6px; 20 margin-bottom: 10px; 21 box-sizing: border-box; 22 background: #f9f9f9; 23 } 24 .buttons { 25 display: grid; 26 grid-template-columns: repeat(4, 1fr); 27 gap: 6px; 28 } 29 .buttons button { 30 padding: 18px 0; 31 font-size: 1.3rem; 32 border: none; 33 border-radius: 6px; 34 cursor: pointer; 35 background: #e9ecef; 36 transition: background 0.15s; 37 } 38 .buttons button:hover { background: #dee2e6; } 39 .btn-operator { background: #ff922b !important; color: #fff; } 40 .btn-operator:hover { background: #f76707 !important; } 41 .btn-equals { background: #228be6 !important; color: #fff; } 42 .btn-equals:hover { background: #1971c2 !important; } 43 .btn-clear { background: #fa5252 !important; color: #fff; } 44 .btn-clear:hover { background: #e03131 !important; } 45 .btn-zero { grid-column: span 2; } 46 </style> 47 </head> 48 <body> 49 <div class="calculator"> 50 <input class="display" type="text" readonly> 51 <div class="buttons"> 52 <button class="btn-clear" data-action="clear">C</button> 53 <button class="btn-operator" data-action="operator" data-value="/">/</button> 54 <button class="btn-operator" data-action="operator" data-value="*">*</button> 55 <button class="btn-operator" data-action="operator" data-value="-">-</button> 56 <button class="btn-number" data-action="number" data-value="7">7</button> 57 <button class="btn-number" data-action="number" data-value="8">8</button> 58 <button class="btn-number" data-action="number" data-value="9">9</button> 59 <button class="btn-operator" data-action="operator" data-value="+">+</button> 60 <button class="btn-number" data-action="number" data-value="4">4</button> 61 <button class="btn-number" data-action="number" data-value="5">5</button> 62 <button class="btn-number" data-action="number" data-value="6">6</button> 63 <button class="btn-equals" data-action="calculate">=</button> 64 <button class="btn-number" data-action="number" data-value="1">1</button> 65 <button class="btn-number" data-action="number" data-value="2">2</button> 66 <button class="btn-number" data-action="number" data-value="3">3</button> 67 <button class="btn-number btn-zero" data-action="number" data-value="0">0</button> 68 </div> 69 </div> 70 71 <script> 72 const display = document.querySelector('.display'); 73 let expression = ''; 74 document.querySelector('.buttons').addEventListener('click', function (e) { 75 const btn = e.target.closest('button'); 76 if (!btn) return; 77 const action = btn.dataset.action; 78 if (action === 'number') { 79 expression += btn.dataset.value; 80 display.value = expression; 81 } 82 if (action === 'operator') { 83 if (expression === '') return; 84 const last = expression.slice(-1); 85 if (['+', '-', '*', '/'].includes(last)) return; 86 expression += btn.dataset.value; 87 display.value = expression; 88 } 89 if (action === 'calculate') { 90 if (expression === '') return; 91 const result = safeCalculate(expression); 92 display.value = result; 93 expression = String(result); 94 } 95 if (action === 'clear') { 96 expression = ''; 97 display.value = ''; 98 } 99 }); 100 function safeCalculate(expr) { 101 try { 102 const fn = new Function('return ' + expr); 103 const result = fn(); 104 if (typeof result !== 'number' || !isFinite(result)) return 'Error'; 105 return Math.round(result * 1e10) / 1e10; 106 } catch (e) { 107 return 'Error'; 108 } 109 } 110 </script> 111 </body> 112 </html>
Salvesta see, ava brauseris, kalkulaator töötab kohe, pole serverit, pole npm install. Praktikas piisab sellest koodist maandumislehe tarnekalkulaatori või allahindluse arvutamise vormi jaoks.

Miks mitte eval(): turvalisus, millest algajate juhendid ei räägi
Kui oled lugenud vanu õpetusi, oled ilmselt näinud eval(expression). Jah, see töötab. Kuid probleem on selles, et eval() käivitab mis tahes JavaScripti koodi, mis on sõnena edastatud. Kui alert(document.cookie) satub ekraanile, siis see käivitatakse. Ja kui ekraan on käsitsi sisestamiseks avatud, saab sinna midagi tõsisemat sisestada.
Meie juhul töötab ekraan readonly režiimis, avaldis koostatakse ainult nuppudele klikkides. Risk on minimaalne. Kuid harjumus eval() kasutada on halb: ühel päeval kopeerid selle mustri kasutaja sisendiga vormi ja saad XSS augu.
On kolm alternatiivi, keerukuse kasvamise järjekorras:
Lähenemine | Kaitse | Keerukus |
|---|---|---|
| Ei näe lokaalseid muutujaid, ainult globaalseid | Minimaalne |
Manuaalne parser (sõne parsimine operaatorite kaupa) | Täielik kontroll avaldise üle | Keskmine |
Web Worker isoleeritud kontekstiga | Avaldis ei puuduta DOM-i üldse | Kõrge |
Õppeotstarbelise kalkulaatori ja enamiku reaalsete stsenaariumide jaoks piisab new Function()-ist koos tulemuse kontrolliga. Kuid kui lisad kalkulaatori e-kaubanduse vormi (hind × kogus), ära üldse vaevu sõnede parsimisega: võta väärtused väljadelt ja arvuta otse: priceInput.value * qtyInput.value. Seal pole eval-i üldse vaja.
Kuidas edasi arendada: klaviatuurisisend ja tehteahelad
Kui põhiversioon töötab, lisa kaks funktsiooni, mis muudavad käsitööriista töövahendiks:
Klaviatuurisisend. Kinnita addEventListener('keydown', ...) document-i külge ja vastenda klahvid nuppudele: numbrid ja punkt data-action="number"-ile, Enter „võrdub"-le, Escape "C"-le, +-*/ operaatoritele. Ignoreeri tähti ja erimärke.
Tehteahelad. Praegu arvutab meie kalkulaator „kõik korraga". Kuid kui vajutad 5 + 3 = ja seejärel * 2, siis see ei jätka, avaldis on juba tulemuseks lähtestatud. Selle parandamine on lihtne: pärast = ära kirjuta expression-i üle, vaid salvesta viimane tulemus eraldi lastResult muutujasse. Järgmise operaatori korral alusta avaldist lastResult-iga.
Mõlemad täiustused on igaüks 15-20 koodirida. Proovi neid ise harjutusena: see on parim viis mõista, et kalkulaator ei ole „õppeprojekt", vaid tavaline inseneri miniülesanne.
⁉️🤔 Korduma kippuvad küsimused
Kas saab ilma JavaScriptita hakkama?
Nelja aritmeetilise tehte jaoks kliendipoolselt, ei. HTML ja CSS on märgendus ja stiilid, nad ei tee arvutusi. Serveripoolne arvutamine PHP-ga tähendab lehe uuesti laadimist iga "=" vajutusega. JavaScript on siin ainus praktiline võimalus. On CSS-i trikke
calc()-i ja loogikat imiteerivate märkeruutudega, kuid see on atraktsioon, mitte kalkulaator: maksimaalselt üks tehe, lähtestamist pole, kohutava koodiga.
Kuidas see lähenemine erineb „võta valmis npm pakett" omast?
Mitte ühtegi sõltuvust.
npm install calculator-libtõmbab kaasa 40 paketti, bundleri ja pool megabaiti, et kahte numbrit liita. Meie variant: üks HTML fail, 90 rida JavaScripti, avaneb igas brauseris ilma serverita. Enamiku tüüpiliste ülesannete jaoks, tarnekalkulaator, laenuarvutus maandumislehel, „arvuta maksumus" vorm, sellest piisab.
Miks mitte jQuery?
jQuery kalkulaatori jaoks on nagu väikebuss leiva toomiseks. 2017. aastal oli sellel veel mõtet:
$('.btn')on lühem kuidocument.querySelectorAll('.btn')ja brauserid käitusid erinevalt. Täna töötavadaddEventListenerjaclosest()kõigis brauserites ühtemoodi ja 87 KB tihendatud jQuery vedamine viie klikikäsitleja jaoks on liialdus. Kui jQuery on projektis juba olemas, kasuta seda, kuid uue koodi jaoks kirjuta puhtas JS-is.
Mida teha ujukomaga? 0.1 + 0.2 annab ikka 0.30000000000000004.
Koodis panime
Math.round(result * 1e10) / 1e10, see eemaldab saba kümne koha ulatuses. Finantsarvutuste jaoks (hinnad, protsendid) kasuta täisarve, arvuta sentides, mitte eurodes:(100 + 200) / 100 = 3.00asemel0.1 + 0.2. Siin poleeval-i egaFunction-i vaja, lihtsalt võta numbrid väljadelt ja liida need.
Kas kirjutada oma kalkulaator või võtta valmis?
Kui vajad kalkulaatorit ühe lehe jaoks, kirjuta see ise tunni ajaga seda juhendit kasutades. Koodi on alla 100 rea, null sõltuvust, sa kontrollid käitumist.
Kui lisad arvutuse WooCommerce'i või tellimisvormi, ära kirjuta avaldise parserit. Võta väärtused sisendväljadelt ja arvuta otse: total = basePrice + optionsSum. See on usaldusväärsem, lihtsam ja turvalisem kui ükski sõne arvutamine.
Artikli kood on töötav vundament. Muuda paigutust, lisa protsendid, mälu (M+/M-/MR nupud), tehete ajalugu. Kirjuta kalkulaator üks kord oma kätega ja küsimus „kuidas see töötab" on igaveseks suletud.



