Sveikųjų skaičių žaidimai

„JavaScipt“ turi automatinį tipų pakeitimą. Tačiau kartais tai sukelia sumaištį. Pvz.,
var k='1', v=2+3+k;
kintamajam v priskirs “51“ (nes 2+3 rezultatas bus sujungtas su teksto eilute “1”). O
var k='1', v=k+2+3;
kintamajam v priskirs “123“ – kas gana logiška. Tačiau po
var k='1', v=+k+2+3;
v reikšmė bus 6 (!). Mat unarinis „+“ suprantamas kaip skaitinis operatorius, todėl teksto eilutė turi būti verčiama į skaitinę reikšmę – nuo šio taško atliekamas tik skaitinės operacijos. Ir nors visa tai atrodo pakankamai logiškai, vis tik galima painiava yra nebloga priežastis naudoti tiesioginį tipo pakeitimą.

Su tokiu patarimu imkime naują pavyzdį. Tarkim, kad kažkokių operacijų su data dėka, ištraukėme teksto eilutę, kuri turi dienos (mėnesyje) reikšmę (tarkim, „01”, „02”, ..., „29”, „30”, „31”), kuri priskirta kintamajam diena. Tada panagrinėkime tokį pavyzdį:
var diena_sk = parseInt(diena); alert(100/diena_sk);

Dalybos veiksmas reikalauja, kad operandai būtų skaitinės reikšmės, todėl prieš tai tekstinę eilutę su dienos reikšme pakeitėme jos skaitine reikšme. Ir visa tai puikiai veikia, bet ... ne visada! Kai kuriais atvekais gaunama dalybos iš nulio klaida (arba "begalybė"). O kodėl – toliau ir bus aiškinama.

„parseInt“ funkcija skirta teksto eilutei paversti skaitine reikšme. Be to, ji leidžia naudoti kitokį nei 10 skaičiavimo pagrindą. Pvz., galima „įvesti“ dvejetainius skaičius:
var b=parseInt('101', 2);
ir kintamajam b bus priskirta reikšmė 5. O štai ir šešioliktainių skaičių įvedimo pavyzdys:
var x=parseInt('AF', 16);
x bus lygus 175.

Be abejo, tai labai patogu. O jei nenurodomas pagrindas – naudojamas 10 - nebent tekslo eilutė prasideda „0x“ (tada imama šešioliktainė reikšmė) arba „0“ (aštuntainiam pagrindui). Šie susitarimai turi ilgą istoriją ir siekia kompiuterių ištakas. O tai reiškia, kad tokie prefiksai gali tapti pavojingais.

Pvz., „01” bus laikomas kaip aštuntainė reikšmė „01”, kuri, vienok, nesiskiria nuo dešimtainio 1. Tačiau problema kyla su „08”, nes 8 nėra leistinas aštuntainis skaitmuo (kaip ir „09“ atveju). Taisyklė yra tokia, kad nesant neleistiniems simboliams, atpažinimas sustoja ties pirmu tokiu simboliu – tad „08” ir “09“ atveju gražinama reikšmė 0. Tai ir yra priežastis, kodėl minėtas pavyzdys gražina dalybos iš nulio klaidą.

Dar paaiškėja, kad „parseInt“ yra kiek skirtingai realizuojamas skirtingose naršyklėse. Pvz., IE9 netaiko “0” prefikso taisyklės “08“ ir „09“ atveju ir skaičių laiko esant dešimtainiu. Vis tik, „parseInt“ funkciją reikia naudoti atsargiai. Minėtame pavyzdyje problemą išsprendžia pagrindo nurodymas, t.y.
var diena_sk = parseInt(diena, 10); alert(100/diena_sk);

Truputis magijos: perrašome parseInt funkciją

Minėta, kad naujausia ECMAScript 5 versija atsisakė pirmąjį 0 laikyti aštuntainio skaičiaus požymiu, tačiau „JavaScript“ versija dar nėra visuotinai paplitusi. Tad vienas iš galimų sprendimų šio neaiškumo pašalinimui būtų – pakeisti parseInt funkciją.

Laimei, tai nėra sudėtinga ir kartu tai puikus pavyzdys, pailiustruojantis, kokia lanksti yra ši kalba.

Tačiau pradžioje reikia trupučio paaiškinimų. parseInt yra globalioji funkcija, o tai reiškia, kad ji yra globalaus objekto, dažniausiai windows, metodas. Tad ją kviesti galime
window.parseInt(n, r)  arba  this.parseInt(n, r)

Tačiau naudojant su „this“, reikia būti tikriems, kad nesame konstruktoriaus funkcijoje arba tai gali reikšti nuorodą į ką nors kita, o ne globalų objektą.

O tada, kaip bet kurio metodo atveju, galima ją apibrėžti kitaip – priskiriant jai anoniminę funkciją, t.y. taip:
parseInt (n, r) = function() { mūsų savas kodas } ;

Ir po tokio jos apibrėžimo, visi parseInt panaudojimai reikš kreipinį į mūsų funkciją.

Belieka parašyti pataisytą šios funkcijos versiją – kas neatrodo labai įdomu ir tarytum tėra laiko švaistymas, nes tenorime tik nežymiai pakeisti šios funkcijos veikimą. Idėja – išsaugoti nuorodą į originalią parseInt funkciją, kuri būtų kviečiama tada, kada tai daryti saugu.

Tuo tikslu parašome set_new_parseInt funkciją:

set_new_parseInt = function () {

 if (parseInt("010") === 10) return;

   var savedOriginal = parseInt;
   parseInt = function(n,r) {
       if (r === undefined) {
          n = n.replace(/^s*/, "");
          if (n.substr(0,2) === "0x") { 
             r = 16;
           } else {
              r = 10;
           } 
        }   
        return savedOriginal(n, r);
    };
}

Dabar išbandykime:

  set_new_parseInt();

  alert(parseInt("0xFF"));   // pateikia 255
  alert(parseInt("111", 2)); // pateikia 7
  alert(parseInt("023"));    // pateikia 23
  alert(parseInt("09"));     // pateikia 9

Taigi, kaip matote, funkcija parseInt veikia puikiai – ir netgi tuo atveju, kai jai perduodamas „problematiškoji“ reikšmė „09“. Beliko paaiškinti kelis aspektus.

Pirmiausia yra patikrinama, gal parseInt funkcija jau veikia taip, kaip tikimės. Tuo tikslu ji iškviečiama ir patikrinamas jos rezultatas. Jei funkcija jau ignoruoja pradinį „0“, tai nieko nedarome ir tiesiog grįžtame iš mūsų funkcijos. Tai atlieka:
if (parseInt("010") === 10) return;

Tada lokaliame kintamajame įsimename nuorodą į originaliąją parseInt funkciją:
savedOriginal = parseInt;

Toliau seka parseInt „patobulinimas“. Mums tereikia tikrinti ir „patikslinti“ atvejį, kai antrasis parametras yra nenurodytas. Tai nustatome naudodami
if (r === undefined)

Toliau belieka patikrinti, ar parametras neprasideda „0x“ (šešioliktainio skaičiaus požymis) ir nurodyti, koks turi būti skaičiavimo sistemos pagrindas (16 ar 10). Ir tada iškviečiama originalioji parseInt funkcija (jau su “patikslintu” antruoju parametru).

Paprasta, ar ne? Visa magija tame, kad įmanoma iškviesti lokaliajame kintamajame įsimintą nuorodą į „originaliąją“ parseInt funkciją. Tai „užsklendimo“ (angl. closure) savybė, kai naujai apibrėžta (parseInt) funkcija gali naudoti funkcijos, kurioje ji buvo apibrėžta, lokaliuosius kintamuosius.

Puikiai išsprendėme problemą! Va! ... tik nereikia užmiršti, kad prieš naudojant patobulintą parseInt, būtina (!) dar iškviesti ir set_new_parseInt(). Tad būtų truputį geriau, jei galėtume taip apibrėžti naująją parseInt, kad jinai savo pirmojo panaudojimo metu save pačią modifikuotų.

Tad mūsų laukia naujas magijos seansas. Mat tiesmukiškas bandymas
parseInt = function () {
var savedOriginal = parseInt;
// ....
}

neišdega, nes kintamajam savedOriginal bus priskirta nuoroda jau į naujai apibrėžiamą funkciją (šios iškvietimo metu). Išeitis – iškviesti funkciją iškart, t.y., tuo metu, kai dar egzistuoja nuoroda į originaliąją parseInt funkciją. Sprendimas būtų toks:

parseInt = function (n, r) {

 if (parseInt("010") === 10) return;

   var savedOriginal = parseInt;

   return (function(n,r) {
       if (r === undefined) {
          n = n.replace(/^s*/, "");
          if (n.substr(0,2) === "0x") { 
             r = 16;
           } else {
              r = 10;
           } 
        }   
        return savedOriginal(n, r);
    });
} ()

Visa magija yra tuose skliausteliuose (), kurie (pačioje pabaigoje ir yra išryškinti raudonai) užbaigia funkcijos apibrėžimą. Jie priverčia iškart vykdyti funkciją ir gražinti nuorodą funkciją, kurią ką tik parseInt< apibrėžė. Jei tų skliaustelių gale nebūtų, gautumėte pranešimą apie steko persipildymą – galite patys pasvarstyti., kodėl taip nutinka. Toliau, vėl panaudojamas tas pats užsklendimo (closure) mechanizmas, užtikrinantis originaliosios funkcijos panaudojimą.

Puiku! Pabandykite ir pamatysite, kaip nuostabiai tai veikia (šįkart nereikia jokių papildomų parseInt iškvietimų). Vienintelis trūkumas, tai papildomas procesoriaus kaitinimas, nes dabar kiekvieno kreipinio į parseInt metu vyksta perteklinis patikrinamas, ar funkcija korektiškai veikia ( if (parseInt("010") === 10)).

Šis pavyzdys puikiai pailiustruoja JavaScript kalbos funkcinius ir dinaminius aspektus. Šią techniką galima panaudoti bet kurios funkcijos „patobulinimui“ – išlaikant galimybę iškviesti ir ankstesniąją tos funkcijos versiją. Nors ... bendruoju atveju tai ir nėra pati geriausia idėja – ir ją panaudoti tereikia, kai to tikrai reikia.

Pastaba: Pabaigoje tiesiog priminsime, kad trys lygybės (===) reiškia, kad simbolių eilučių palyginimo metu nereikia atlikinėti jų konversijų į sveikus skaičius [ ir jei if ("3" == 3) reiškia, kad sąlyga patenkinta, tai if ("3" === 3) atveju, ji nepatenkinta (nes 6iuo atvejueilutė "3" nėra lygi sveikam skaičiui 3) ].

Ankstesnės "Advanced HTML" skyrelio temos:
JavaScript atspindžiai
Dygios JavaScript eilutės
Pelė uodega švystelėjo...
Anotacijos Java kalboje
AWK kalba - sena ir nuolat aktuali
CGI.pm biblioteka: sausainiai
Ką delne mums neša HTML 4.0?
Kaip valdyti piešinių pakrovimo tvarką
Kaip lankytoją nukreipti į kitą WWW puslapį
Bilas Geitsas: kol dar nebuvo garsus
Pitonas, kandantis sau uodegą
Viešojo rakto kriptografija
Tikroji Interneto pabaiga
Įlįskite į lankytojų kailį
Vaizdi rašysena - VB Script
ASP patarimų liūnas
Debesies architektūra
Ruby on Rails

JavaScript pradmenys
Viešojo rakto kriptografija
Džonas Bakas – FORTRAN tėvas
Nutylimųjų savybių ieškant
Vartiklis