let && const || var

Zakládní rozdíl?

Starší var je funcional-scoped. Naopak let a const jsou block-scoped. Block-scoped znamená, že proměnné existují jen v bloku, který je obaluje. Pokud var neobalíme funkcí, pak nám vytvoří property do globálního objektu (window/global), zatímco let a const properties do globalního objektu nevytvářejí.

var a = 'a';

{
   var b = 'b';
}

if(true){
   var c = 'c';
}

// o arrow funkcích v některém z přístích článků
// funkce vytváří nový scope pro var
() => {
   var d = 'd';
}

console.log(window.a); // 'a';
console.log(window.b); // 'b';
console.log(window.c); // 'c';
console.log(window.d); // undefined

Už jste slyšeli o pojmu hoisting? Jde o to, že deklarace proměnných jsou posouvány nahoru v aktuálním scopu. Je tedy dostupná dříve než jí deklarujete. Tímto trpí proměnné deklarované pomocí klíčového slova var.

(function foo(){
   // zde je proměnná x automaticky nastavené na undefined
   console.log(x); // undefined
   x = 5;
   console.log(x); // 5
   // …
   var x;
   console.log(x); // 5
}()) // IIFE - immediately-invoked functional expression

U nového let ani const hoisting neplatí, za to platí temporal dead zone (TDZ).

{ 
   // pro let a const není třeba vytvářet IIFE, protože vytváří proměné platné pouze v bloku
   // zde začíná temporal dead zone (TDZ), je zde vytvořený binding

   // tělo funkce je voláno, až po ukončení TDZ (řádek A), tedy zde už je x možné použít
   let func = function(){
      console.log(x); // 1
   }
   
   console.log(x); // ReferenceError
   x = 1; // ReferenceError
   console.log(typeof x); // nelze použít ani typeof - ReferenceError
   // …
   let x; // zde končí temporal dead zone, x je nastavená na undefined
   console.log(x); // undefined
   x = 1;
   console.log(x); // 1
   func(); // (A)
}

Hmmm, tak to všechno přepíšeme do toho letu, né?

Zní to lákavě, ale přepisovaní starého kódu v ES5 do nové specifikace ES6 může změnit funkcionalitu. A stejně tak tomu je u let,const a var. Takže pozor na to!

var x = 1;
function foo(value){
   // zde x překryje dřívější definici a je nastavená na undefined - hoisting
   if(value === true){
      var x = 2;
      return x;
   }
   return x;
}

foo(true); // 2
foo(false); // undefined
let x = 1;
function foo(value){
   if(value === true){
      let x = 2;
      return x;
   }
   return x;
}

foo(true); // 2
foo(false); // 1

Jak se mění chování v cyklech?

{
   let a = [];
   for(var i = 0; i < 3; i++){
      a.push(() => i); // (A)
   }
   a.map(x => x()); // [3,3,3] (B)
}

O arrow funkcích (nově v ES6) někdy v dalším článku. Prozatím uvedu ekvivalentní zápis řádku A a B v ES5.

a.push(function() { return i; }); // (A)
a.map(function(func) { return func(); }); // (B)

Každý cyklus vytvoří funkci, která vrací hodnoty proměnné i. V době volání těchto funkcí je binding pro i nastavený na stejné místo v paměti, které obsahuje hodnotu 3. To znamená, že var vytvoří pro všechny cykly jeden binding.

{
   let a = [];
   // zde je v každém cyklu vytvořený nový binding
   for(let i = 0; i < 3; i++){
      a.push(() => i);
   }
   a.map(x => x()); // [0,1,2]
}

Jak se mění chování pro parametry funkcí?

Defaultní proměnné jsou také novinkou ES6 a bude o nich řeč v některých z dalších článků. Prozatím si řekněme, že jsou zde vytvářené jako sekvence let proměnných, tedy existuje temporal dead zone.

// p2 může použít p1
function foo(p1 = 1, p2 = p1, p3){ 
   let p1; // static error: duplicitně vytvořené p1
   var p3; // neudělá vůbec nic
   {
      let p1; // překryje p1, už jsme v novém bloku
   }
}

Pokud parametry přehodíme, tak se zde snažíme přistoupit k parametru p2 v jeho temporal dead zone, což není povoleno.

// zde dojde k chybě, protože p2 je v temporal dead zone
function foo(p1 = p2, p2 = 2){ 
}

Defaultní proměnná nemá přístup ke scopu funkce.

let x = 1;
function foo(func = y => x){    
let x = 2;    
console.log(func()); // 1
}

Nakonec si řekněme, kdy požívat let a const a kdy požívat var.

// const lze využívat pro immutable objekty
const C1 = new Symbol(); // o Symbolech si řekněme v některém z dalších článku
const C2 = 1;
const C3 = Object.freeze([]); // freeze “zmrazí objekt” (v tomto případě pole) - nelze měnit obsah
// let lze používat pro mutable objekty
let obj = {};
obj.prop = 2;
let x = 3;
x = 5;
// const je samozřejmě možné používat i pro mutable objekty
const obj = {};
obj.prop = 1;

Referecnce

ES6 scoping od famozního Axel Rauschmayer

Hluboký pohled ze série ES6 in depth

Author

Jiří Otáhal

comments powered by Disqus