Jaký je rozsah proměnných v JavaScriptu?

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:

  1. Globální – viditelné všem
  2. Funkce – viditelná v rámci funkce (a jejích podfunkcí a bloků)
  3. Blok – viditelné v rámci bloku (a jeho dílčích bloků)
  4. 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:

  1. Jak byl identifikátor deklarován
  2. Kde byl deklarován identifikátor
  3. Ať jste v přísném nebo nepřísném režimu

Některé ze způsobů, jak lze identifikátory deklarovat:

  1. var , let a const
  2. Parametry funkcí
  3. Parametr bloku catch
  4. Deklarace funkcí
  5. Výrazy pojmenovaných funkcí
  6. Implicitně definované vlastnosti na globálním objektu (tj. chybí var v nepřísném režimu)
  7. import prohlášení
  8. eval

Některé z identifikátorů umístění lze deklarovat:

  1. Globální kontext
  2. Tělo funkce
  3. Obyčejný blok
  4. Začátek ovládací struktury (např. smyčka, if, while atd.)
  5. Tělo řídicí struktury
  6. 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 04 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.