Publikoval Michal Kočí dňa 18.11.2016 o 20:12 v kategórii JavaScript
Dnes sa pozrieme na lexikálny scope, povieme si čo to je a prečo by sme o ňom mali vedieť, sko s ním súvisí hoisting a čo to ten hoisting vlastne je. Preto ak vám tieto pojmy nič moc nevravia, čítajte ďalej...
Poďme najskôr na definície:
Zatiaľ docela nuda, ale nebojte sa, čoskoro sa do toho kompletne zamotáme. Predstavte si nasledujúci príklad:
function logConstant() { var number = 3;
console.log(number); } logConstant();
Kde je lexikálny scope premennej number
? Vo funkcii logConstant
? Správne!
Tak poďme na ďalší príklad:
function logTwoConstants() { for(var i=0; i<2; i++) { var number = i * 2; console.log(number); } console.log(number); } logTwoConstants();
Kde je lexikálny scope premennej number
teraz? Je to funkcia logTwoConstants
? Alebo je to for
blok? Správne, je to funkcia logTwoConstants.
Pýtal sa niekto prečo? Pretože v JavaScripte (ES5) je scope v rámci funkcie, nie v rámci bloku. Čo môže mýliť programátorov prichádzajúcich z iných jazykov céčkového typu, ale vy sa pomýliť nedajte.
Potvrdenie tohto faktu som vám nechal priamo v príklade. Ak by scope premennej number
bol v rámci bloku, za for
blokom by ste prístup k tejto premennej už nemali. Ale ak si príklad spustíte, uvidíte, že sa do konzoly vypíše číslo 2, t.j. posledná priradená a zároveň aktuálna hodnota.
Tým sa dostávame k ďalšiemu terminus technikus (teda technickému pojmu) a tým je hoisting. Krásny vznešený názov, ale nečakajte žiadnu raketové vedu.
Hoisting sa deje počas spustenia vášho skriptu a deje sa to, že deklarácie premenných nastanú ako úplne prvé v rámci scopu. Najčastejšie sa zjednodušene vraví, že deklarácie premmených sa presunú na začiatok ich scopu. Nie je to technicky úplne správne, ale na pochopenie to stačí.
Takže, ak sa vrátime k predchádzajúcemu príkladu. Ak by ste sa na funkciu logTwoConstants
pozreli očami behového prostredia (t.j. toho čo ju spúšťa), videli by ste ju asi takto:
function logTwoConstants() { var i, number; for(i=0; i<2; i++) { number = i * 2; console.log(number); } console.log(number); }
Ak ste náhodou boli v predchádzajúcej verzii tejto funkcie stále zmätený, ako je možné, že console.log
za for
cyklom vidí premennú number
, teraz už by vám to malo byť jasné. Jej deklarácia nastane na začiatku scopu a tak je reálne jedno, kde ju vy fyzicky deklarujete.
Samozrejme, nasledovný zápis by ste isto nenapísali, ale funkčnosť funkcie je úplne identická ako jej predchádzajúce dve verzie:
function logTwoConstants() { for(i=0; i<2; i++) { number = i * 2; console.log(number); } console.log(number); var i, number; }
Je ozaj jedno, kde presne vo funkcii var
použijete, pri jej spustení sa to tvári, že ste var
použili úplne na jej začiatku. Preto sa často odporúča, aby ste to písali tak, ako to "vidí" prostredie - na začiatku fukcie nadeklarovali všetky lokálne premenné. Môžete tak navyše pomôcť programátorovi, ktorý s kódom bude pracovať po vás - bude hneď vidieť, ktoré premenné sú lokálne.
Zatiaľ som tvrdil, že lexikálny scope premennej je vo funckii, v ktorej je definovaná. Toto je ale len čiastočká pravda.
Pravidlá sú trochu, ale ozaj len trochu komplikovanejšie. Sú nasledovné:
Chcete príklad? Ale samozrejme...
var globalNumber = 1; function logSomeConstants() { var localNumber = 2; alsoGlobalNumber = 3; console.log(globalNumber); console.log(localNumber); console.log(alsoGlobalNumber); } logSomeConstants(); console.log(globalNumber); console.log(alsoGlobalNumber); console.log(window.globalNumber); console.log(window.alsoGlobalNumber);
Na príklade vidíte potvrdenie môjho predchádzajúceho tvrdenia a zároveň ukážku toho, čo je to ten globálny scope. V prípade, že váš skript beží v prehliadači, globálny scope je objekt window
. Takže, čo si zadefinujete na globálnom scope, k tomu viete pristúpiť priamo, alebo cez window
.
V prípade, že skript spúšťate v node.js, tam samozrejme objekt window
k dispozícii nie je (lebo reálne žiadne okno neexistuje), tam je globálny objekt dostupný pod názvom global
.
Teória je to zaujímavá, ale k čomu vám všetky tieto vedomosti sú v praxi? Hneď na prvý pohľad viete povedať, či bude premenná lokálna, alebo globálna. A to je dôležité.
Totiž, predstavte si, že váš skript neexistuje v stránke osamotene, čo je veľmi pravdepodobné. Stránka načítava viac skriptov a pokiaľ viaceré z nich deklarujú globálne premenné a navyše ich názvy sú rovnaké, tak ste v... vo veľmi ošemetnej situácii. Ide o tzv. kolíziu názvov a takéto problémy naozaj riešiť nechcete, verte mi.
Druhým problémom, ktorý je v mierne teoretickej rovine je rýchlosť vašej aplikácie. Pokiaľ niekde pristupujete k premennej, behové prostredie ju musí nájsť.
Napríklad výraz console.log(number)
znamená, že chcete spustiť funkciu log
na objekte console
a chcete jej ako hodnotu prvého parametra poslať hodnotu premennej number
.
Prostredie sa teda pozrie, či na aktuálnom, lokálnom scope máte deklarovanú premennú tohto názvu. Pokiaľ máte, použije ju (resp. jej hodnotu).
Pokiaľ nemáte, behové prostredie sa pozrie ďalej - na globálny scope. Teda, prvé teoretické spomalenie nastane v dôsledku toho, že musí prehľadať nie jeden, ale dva scopy.
No a druhé spomalenie môže nastať preto, lebo je globálny scope zapratený obrovským množstvo premenných, ktoré tam nasekali programátori neuvedomujúc si dôsledky a navyše pravdepodobne úplne nezmyselne. Globálne premenné sú potrebné veľmi, naozaj veľmi zriedka.
Keď už chápete lexikálny scope, nabudúce sa pozrieme na fantastickú vec a tou budú closures. A vysvetlíme si ako fungujú, aby vás nič neprekvapilo. Takže, určite ostaňte naladení...
Chcete sa poriadne naučiť JavaScript a ešte sa pritom naučiť nejaký framework či knižnicu (napríklad Angular či React)? Prídite na niektoré z mojich JavaScriptových školení a naučte sa to rýchlo a jednoducho.
Ak nechceš premeškať príspevky ako je tento, sleduj ma na Twitteri, alebo ak máš RSS čítačku, môžeš sledovať môj RSS kanál.