JavaScript kintamųjų galiojimo sritys
Dažnai jauniesiems programuotojams savo kelionę į JavaScript pradėjusiems nuo C++, Java ir panašių, į klases orientuotų objektinių programavimo kalbų neaišku kaip veikia this kintamasis. Priešingai, nei aukščiau paminėtose kalbose this gali įgyti skirtingas reikšmes priklausomai nuo to, kaip iškviečiama šį kintamąjį naudojanti funkcija.
Kitas susijęs kalbos niuansas — paprastų kintamųjų galiojimo sritys. Skirtingai, nei C ar Java šeimų kalbose, kintamųjų galiojimas neapsiriboja vien blokais tarp { } skliaustų. JavaScript’e galima išskirti dvi kintamųjų galiojimo sritis:
Globali sritis
Globalioje srityje aprašyti kintamieji yra pasiekiami visame kode. Kitaip sakant jeigu kintamasis aprašytas ne funkcijoje, jį mato visos funkcijos. Verta žinoti, kad globalioje srityje aprašyti kintamieji automatiškai priskiriami globaliam objektui, kuris naršykių atveju vadinasi „window“, o kitose ECMAScript implementacijose gali ir skirtis. Štai kodėl galima parašyti tokį kodą:
1 | var globalus = 7; |
Lokali sritis
Lokalioje srityje sukurti kintamieji galioja tik toje srityje ir iš tos srities sukurtose srityse. Jei nesupratote pirmojo sakinio, šis turėtų būti aiškesnis: kintamąjį aprašius funkcijoje jis galioja ir toje funkcijoje aprašytoms funkcijoms. Jeigu vis tiek nesupratote, panagrinėkime pavyzdį:
1 | function a() { |
Kas įvyks įvykdžius kodą?
- Sukuriama globali kintamųjų galiojimo sritis, į kurią įdedamos funkcijos a() ir b().
- Iškviečiama funkcija b() ir jai sukuriama nauja kintamųjų galiojimo sritis, į kurią įdedami du kintamieji: „kintamasis“ ir funkcija c() (JavaScript’e ir funkcijos yra objektai).
- Iškviečiama funkcija c(), kuriai sukuriama nauja kintamųjų galiojimo sritis, tačiau į ją nieko neįdedama.
- Kviečiamas alert() metodas, kintamojo „kintamasis“ ieškoma funkcijos c() srityje, tačiau joje jo nėra… Ką daryti? Ogi ieškoti išorinėje srityje, kuri šiuo atveju priklauso funkcijai b(). Kadangi čia jis aprašytas, jis ir naudojamas. Jeigu jo čia nebūtų, būtų imama išorinė sritis ir kintamojo „kintamasis“ būtų ieškoma globalioje srityje (ieškotų window.kintamasis ).
- Įvykdę funkciją c() grįžkime prie darbo ir toliau vykdykime a(). Tik prieš grįždami į funkcijos b() sritį nepamirškime ištrinti c() kintamųjų galiojimo srities!
- Funkcijos a() vykdymas beveik nesiskiria nuo c(): sukuriama nauja sritis, vykdomas alert(), ieškomas „kintamasis“: a() srityje, jo ten nėra, todėl ieškoma išorinėje (kuri šiuo atveju yra globali), jo ten nėra, todėl ieškoma išorinės išorinėje… Bet globali sritis neturi išorinės srities! Taip supykdytas JavaScript interpretatorius tėkš jums į veidą bjaurų exceptioną:
Unhandled Error: Undefined variable: kintamasis, kurio Jūs ir nusipelnėte, nes bandėte panaudoti kintamąjį, kuris nėra aprašytas jokioje iš funkcijos a() pasiekiamoje kintamųjų galiojimo srityje.
Išsigandę šio exceptiono tikriausiai visą gyvenimą bijosite parašyti kažką panašaus į šį kodą:
1 | function a() { |
Galiu Jus nuraminti, JavaScript (kaip jau minėjau aukščiau) kintamųjų sritys paremtos ne blokais tarp { } skliaustų, o funkcijos lygio sritimis. Kadangi „for“ nėra funkcija, ten aprašyti kintamieji galios visoje a() srityje. Štai kodėl įvykdžius funkciją a(), bus parodyti du dialogai: pirmasis su skaičiumi 4, antrasis su skaičiumi 5.
Jeigu jau nurimote, galiu išgąsdinti vėl: pamiršus prirašyti žodelį „var“ sukuriant kintamąjį, jis sukuriamas kaip globalus kintamasis. Todėl kode
1 | function b() { |
funkcijos a() ciklas įvykdys tik vieną iteraciją. Vienintelis būdas nuo to apsisaugoti, tai naudoti naują JavaScript 1.8.5 griežtąjį režimą “strict mode”. Tada bandant sukurti kintamąjį be „var“ išmes exceptioną.
Anoniminės funkcijos
Perskaičius tiek daug teksto tikriausiai jums galvoje knibžda tik viena mintis: o kaip užrašyti funkciją, kuri nepriklausytų jokiai kintamųjų galiojimo sričiai ir ar tai iš vis įmanoma? Atsakymas paprastas: JavaScript nieko nėra neįmanomo! Tam galima panaudoti anonimines funkcijas. Tai tokios funkcijos, kurios neturi pavadinimo. Tokią funkciją galima užrašyti šitaip:
1 | (function() { |
Kadangi ši funkcija neturi pavadinimo, ji nepriklauso jokiai kintamųjų sričiai (jos neįmanoma iškviesti), tačiau joje galioja išorinės srities kintamieji (duotame pavyzdyje globalūs kintamieji).
Jeigu jau išmokote susikurti funkciją, kurios negalite iškviesti, galvojate ką su ja daryti? Daug nepadarysite, tėra tik du panaudojimo būdai: galite ją priskirti kokiam nors kintamajam ir taip pašalinti jos anonimiškumą:
1 | var a = function() { |
arba galite įtikinti anoniminę funkciją išsikviesti save pačią:
1 | (function() { |
Pastarasis būdas yra labai mėgstamas įvairių bibliotekų kūrėjų, nes leidžia sukurti kintamųjų sritį neužteršiant globaliosios:
1 | $ = PrototypeJS; |
Funkcijų iškvietimas ir „this“
Panagrinėkime pavyzdį:
1 | function a() { |
Kokį rezultatą galime gauti iškvietus funkciją, kokia gali būti this reikšmė? Lengviau atsakyti į klausimą kokia negali būti this reikšmė. Ji negali būti null ir undefined, o visos kitos reikšmės yra įmanomas variantas. Kintamasis this gali tapti netgi drambliu, tačiau norint tuo įsitikinti jums reikėtų žinoti vieną paprastą faktą: funkcijos iškvietimas naudojant konstrukciją a()
yra naudojamas tik nemokšų ir tinginių, kurie tingi užrašyti tikrąją, nesutrumpintą formą:
1 | a.call({dramblys: true} /*this reikšmė*/, "kiti", "prametrai")` |
arba
1 | a.apply({dramblys: true} /*this reikšmė*/, ["kiti", "prametrai"])` |
Kaip matote vietoj this reikšmės galite įrašyti visiškai ką norite, tik jeigu ten įrašysite null arba undefined, this bus automatiškai pakeistas į globalų objektą (naršyklių atveju į „window“).
Taigi, dabar, kai žinote tikrąjį būdą iškviesti funkciją galite pradėti mokytis sutrumpinimų, nenaudojant nei „call“, nei „apply“. Panagrinėkime aukščiau pateiktą atvejį:
1 | a() |
Remiantis tuo, kad a() aprašytas globalioje srityje, šį užrašymą galima pakeisti į
1 | window.a() |
Iškviečiant funkciją sutrumpintu būdu vietoj „this“ paduodamas prieš tašką buvęs objektas, kuris mūsų pavyzdžio atveju yra „window“, todėl aukščiau pateiktą pavyzdį nesutrumpintu būdu galima užrašyti šitaip:
1 | a.call(window) |
Dabar, kai jau visiškai suprantame sutrumpinto funkcijos iškvietimo veikimą, pabandykime paduoti funkcijai a() dramblį sutrumpintu būdu:
1 | var dramblys = {dramblys: true}; |
Sakote kad trys eilutės per daug ir niekas čia nesutrumpėjo? Jūs visiškai teisus(i), nes viską galima sutalpinti į vieną paprastą eilutę:
1 | ({dramblys: true, a: a}).a() |
arba, kad būtų sudėtingiau suprasti:
1 | ({dramblys: true, _: a})._() |
O ką daryti, jeigu norite, kad this visą laiką būtų toks ir ne kitoks? Ogi teks pritaikyti žinias, įgytas pirmojoje straipsnio dalyje ir sukurpti kodą, panašų į šį:
1 | var bind = function(func, thisValue) { |
Dabar galime nesibaiminti, kad iškvietę a()
gausime neaiškų „this“. Panaudojus „bind“ taip, kaip parodyta pavyzdyje, this visada bus dramblys, nes „func“ ir „thisValue“ atsiduria atskiroje kintamųjų galiojimo srityje, priklausančioje funkcijai „bind“.
Jeigu naudojate modernų JavaScript interpretatorių jums pasisekė netgi dar labiau: funkcijos „bind“ jums nebereikia rašyti — ją jau parašė interpretatoriaus kūrėjai, todėl norėdami užtikrinti, kad funkciją kviestų tik ir vien tik dramblys galite ją užrašyti šitaip:
1 | a = a.bind({dramblys: true}); |
Ši funkcija turi dar vieną privalumą: jeigu nurodysite dar parametrų, jie bus kaskart paduodami funkcijai, kurią bind-inote.
1 | var fun = (function (a, b, c, d){ |