Jaký je rozsah proměnných v javascriptu? Mají stejný rozsah uvnitř i vně funkce? Nebo na tom dokonce záleží? Také, kde jsou proměnné uloženy, pokud jsou definovány globálně?
Odpověď
TLDR
JavaScript má lexikální (také nazývaný statický) rozsah a uzávěry. To znamená, že můžete zjistit rozsah identifikátoru pohledem na zdrojový kód.
Čtyři rozsahy jsou:
- Globální – viditelné všem
- Funkce – viditelná v rámci funkce (a jejích podfunkcí a bloků)
- Blok – viditelné v rámci bloku (a jeho dílčích bloků)
- Modul – viditelné v modulu
Mimo speciální případy globálního a modulového rozsahu jsou proměnné deklarovány pomocí var
(rozsah funkce), let
(rozsah bloku) a const
(blokový rozsah). Většina ostatních forem deklarace identifikátoru má rozsah bloku v přísném režimu.
Přehled
Rozsah je oblast kódové základny, ve které je platný identifikátor.
Lexikální prostředí je mapování mezi názvy identifikátorů a hodnotami s nimi spojenými.
Rozsah je tvořen propojeným vnořením lexikálních prostředí, přičemž každá úroveň vnoření odpovídá lexikálnímu prostředí kontextu provádění předchůdce.
Tato propojená lexikální prostředí tvoří rozsahový „řetězec“. Rozlišení identifikátorů je proces hledání shodného identifikátoru v tomto řetězci.
K rozlišení identifikátoru dochází pouze jedním směrem:směrem ven. Tímto způsobem vnější lexikální prostředí nemohou „vidět“ do vnitřních lexikálních prostředí.
Při rozhodování o rozsahu identifikátoru v JavaScriptu existují tři relevantní faktory:
- Jak byl identifikátor deklarován
- Kde byl deklarován identifikátor
- Ať jste v přísném nebo nepřísném režimu
Některé ze způsobů, jak lze identifikátory deklarovat:
var
,let
aconst
- Parametry funkcí
- Parametr bloku catch
- Deklarace funkcí
- Výrazy pojmenovaných funkcí
- Implicitně definované vlastnosti na globálním objektu (tj. chybí
var
v nepřísném režimu) import
prohlášeníeval
Některé z identifikátorů umístění lze deklarovat:
- Globální kontext
- Tělo funkce
- Obyčejný blok
- Začátek ovládací struktury (např. smyčka, if, while atd.)
- Tělo řídicí struktury
- Moduly
Styly deklarací
var
Identifikátory deklarované pomocí var
mají rozsah funkcí , kromě případů, kdy jsou deklarovány přímo v globálním kontextu, v takovém případě jsou přidány jako vlastnosti do globálního objektu a mají globální rozsah. V eval
jsou pro jejich použití samostatná pravidla funkce.
nechat a konstatovat
Identifikátory deklarované pomocí let
a const
mají rozsah blokování , kromě případů, kdy jsou deklarovány přímo v globálním kontextu, v takovém případě mají globální rozsah.
Poznámka:let
, const
a var
jsou všechny zvednuté. To znamená, že jejich logická pozice definice je vrcholem jejich obklopujícího rozsahu (blok nebo funkce). Nicméně proměnné deklarované pomocí let
a const
nelze číst nebo přiřadit, dokud ovládací prvek neprojde bodem deklarace ve zdrojovém kódu. Meziobdobí je známé jako dočasná mrtvá zóna.
function f() { function g() { console.log(x) } let x = 1 g() } f() // 1 because x is hoisted even though declared with `let`!
Názvy parametrů funkcí
Názvy parametrů funkcí jsou omezeny na tělo funkce. Všimněte si, že v tom je mírná složitost. Funkce deklarované jako výchozí argumenty uzavírají seznam parametrů, nikoli tělo funkce.
Deklarace funkcí
Deklarace funkcí mají rozsah bloku v přísném režimu a rozsah funkcí v nepřísném režimu. Poznámka:Nepřísný režim je složitá sada nově vznikajících pravidel založených na nepředvídatelných historických implementacích různých prohlížečů.
Výrazy pojmenovaných funkcí
Výrazy pojmenovaných funkcí jsou omezeny samy na sebe (např. pro účely rekurze).
Implicitně definované vlastnosti na globálním objektu
V nepřísném režimu mají implicitně definované vlastnosti globálního objektu globální rozsah, protože globální objekt je na vrcholu řetězce rozsahu. V přísném režimu to není povoleno.
hodnota
V eval
řetězce, proměnné deklarované pomocí var
bude umístěn v aktuálním rozsahu, nebo, pokud eval
se používá nepřímo jako vlastnosti na globálním objektu.
Příklady
Následující způsobí chybu ReferenceError, protože názvyx
, y
a z
nemají žádný význam mimo funkci f
.
function f() { var x = 1 let y = 1 const z = 1 } console.log(typeof x) // undefined (because var has function scope!) console.log(typeof y) // undefined (because the body of the function is a block) console.log(typeof z) // undefined (because the body of the function is a block)
Následující způsobí chybu ReferenceError pro y
a z
, ale ne pro x
, protože viditelnost x
není blokem omezen. Bloky, které definují těla řídicích struktur, jako je if
, for
a while
, chovejte se podobně.
{ var x = 1 let y = 1 const z = 1 } console.log(x) // 1 console.log(typeof y) // undefined because `y` has block scope console.log(typeof z) // undefined because `z` has block scope
V následujícím x
je viditelný mimo smyčku, protože var
má rozsah funkcí:
for(var x = 0; x < 5; ++x) {} console.log(x) // 5 (note this is outside the loop!)
…kvůli tomuto chování musíte být opatrní při uzavírání proměnných deklarovaných pomocí var
ve smyčkách. Existuje pouze jedna instance proměnné x
deklarován zde a logicky se nachází mimo smyčku.
Následující vytiskne 5
, pětkrát a poté vytiskne 5
pošesté pro console.log
mimo smyčku:
for(var x = 0; x < 5; ++x) { setTimeout(() => console.log(x)) // closes over the `x` which is logically positioned at the top of the enclosing scope, above the loop } console.log(x) // note: visible outside the loop
Následující vytiskne undefined
protože x
má blokový rozsah. Zpětná volání probíhají jedno po druhém asynchronně. Nové chování pro let
proměnné znamená, že každá anonymní funkce se uzavřela nad jinou proměnnou s názvem x
(na rozdíl od var
), a tak celá čísla 0
až 4
jsou vytištěny.:
for(let x = 0; x < 5; ++x) { setTimeout(() => console.log(x)) // `let` declarations are re-declared on a per-iteration basis, so the closures capture different variables } console.log(typeof x) // undefined
Následující NEVYHODÍ ReferenceError
protože viditelnost x
není omezen blokem; vypíše však undefined
protože proměnná nebyla inicializována (kvůli if
prohlášení).
if(false) { var x = 1 } console.log(x) // here, `x` has been declared, but not initialised
Proměnná deklarovaná v horní části for
smyčky pomocí let
je omezen na tělo smyčky:
for(let x = 0; x < 10; ++x) {} console.log(typeof x) // undefined, because `x` is block-scoped
Následující vyvolá ReferenceError
protože viditelnost x
je omezeno blokem:
if(false) { let x = 1 } console.log(typeof x) // undefined, because `x` is block-scoped
Proměnné deklarované pomocí var
, let
nebo const
všechny jsou určeny pro moduly:
// module1.js var x = 0 export function f() {} //module2.js import f from 'module1.js' console.log(x) // throws ReferenceError
Následující deklaruje vlastnost na globálním objektu, protože proměnné jsou deklarovány pomocí var
v globálním kontextu jsou přidány jako vlastnosti do globálního objektu:
var x = 1 console.log(window.hasOwnProperty('x')) // true
let
a const
v globálním kontextu nepřidávají vlastnosti ke globálnímu objektu, ale stále mají globální rozsah:
let x = 1 console.log(window.hasOwnProperty('x')) // false
Parametry funkce lze považovat za deklarované v těle funkce:
function f(x) {} console.log(typeof x) // undefined, because `x` is scoped to the function
Parametry bloku catch jsou omezeny na tělo bloku catch:
try {} catch(e) {} console.log(typeof e) // undefined, because `e` is scoped to the catch block
Výrazy pojmenované funkce jsou omezeny pouze na samotný výraz:
(function foo() { console.log(foo) })() console.log(typeof foo) // undefined, because `foo` is scoped to its own expression
V nepřísném režimu mají implicitně definované vlastnosti globálního objektu globální rozsah. V přísném režimu se zobrazí chyba.
x = 1 // implicitly defined property on the global object (no "var"!) console.log(x) // 1 console.log(window.hasOwnProperty('x')) // true
V nepřísném režimu mají deklarace funkcí rozsah funkcí. V přísném režimu mají blokový rozsah.
'use strict' { function foo() {} } console.log(typeof foo) // undefined, because `foo` is block-scoped
Jak to funguje pod kapotou
Rozsah je definován jako lexikální oblast kódu, pro kterou je platný identifikátor.
V JavaScriptu má každý funkční objekt skrytý [[Environment]]
odkaz, který je odkazem na lexikální prostředí kontextu provádění (rámec zásobníku), v němž byl vytvořen.
Když vyvoláte funkci, skrytý [[Call]]
se nazývá metoda. Tato metoda vytváří nový kontext provádění a vytváří spojení mezi novým kontextem provádění a lexikálním prostředím funkčního objektu. Dělá to zkopírováním [[Environment]]
hodnotu na funkčním objektu do vnějšího referenčního pole v lexikálním prostředí nového kontextu provádění.
Všimněte si, že toto spojení mezi novým prováděcím kontextem a lexikálním prostředím funkčního objektu se nazývá uzavření.
V JavaScriptu je tedy rozsah implementován prostřednictvím lexikálních prostředí propojených v „řetězci“ vnějšími odkazy. Tento řetězec lexikálních prostředí se nazývá rozsahový řetězec a k rozlišení identifikátorů dochází hledáním shodného identifikátoru směrem nahoru v řetězci.
Zjistěte více.