Proč to nefunguje?
function getLogger(arg) {
function logger() {
console.log(arg)
}
return logger
}
let fruit = 'raspberry'
const logFruit = getLogger(fruit)
logFruit() // "raspberry"
fruit = 'peach'
logFruit() // "raspberry" Wait what!? Why is this not "peach"?
Abych si tedy promluvil, co se zde děje, vytvářím proměnnou nazvanoufruit
a jeho přiřazení k řetězci 'raspberry'
, pak předám fruit
na funkci, která vytvoří a vrátí funkci nazvanou logger
který by měl logovat fruit
při zavolání. Když tuto funkci zavolám, dostanu console.log
výstup 'raspberry'
podle očekávání.
Ale pak znovu přiřadím fruit
na 'peach'
a zavolejte logger
znovu. Ale místo získání console.log
z nové hodnoty fruit
, dostanu oldvalue fruit
!
Mohu to obejít voláním getLogger
znovu získat nový zapisovač:
const logFruit2 = getLogger(fruit)
logFruit2() // "peach" what a relief...
Ale proč nemohu jednoduše změnit hodnotu proměnné a získat logger
tologovat nejnovější hodnotu?
Odpovědí je fakt, že když v JavaScriptu voláte funkci s argumenty, argumenty, které předáváte, jsou předávány hodnotou, nikoli odkazem. Dovolte mi stručně popsat, co se zde děje:
function getLogger(arg) {
function logger() {
console.log(arg)
}
return logger
}
// side-note, this could be written like this too
// and it wouldn't make any difference whatsoever:
// const getLogger = arg => () => console.log(arg)
// I just decided to go more verbose to keep it simple
Když getLogger
se nazývá logger
funkce je vytvořena. Je to úplně nová funkce. Když je vytvořena zbrusu nová funkce, rozhlédne se po všech proměnných, ke kterým má přístup, a "uzavře" je za účelem vytvoření toho, co se nazývá "uzavření". To znamená, že pokud je toto logger
existuje, bude mít přístup k proměnným ve funkci svého rodiče a dalším proměnným na úrovni modulu.
Co tedy proměnné dělá logger
mít přístup, když je vytvořen? Když se znovu podíváte na příklad, bude mít přístup k fruit
, getLogger
, arg
a logger
(sám). Přečtěte si tento seznam znovu, protože je zásadní pro to, proč kód funguje tak, jak funguje. Všimli jste si něčeho? Oba fruit
a arg
jsou uvedeny, i když mají přesně stejnou hodnotu!
To, že je dvěma proměnným přiřazena stejná hodnota, neznamená, že jde o stejnou proměnnou. Zde je zjednodušený příklad tohoto konceptu:
let a = 1
let b = a
console.log(a, b) // 1, 1
a = 2
console.log(a, b) // 2, 1 ‼️
Všimněte si, že i když děláme b
přejděte na hodnotu proměnné a
, dokázali jsme změnit proměnnou a
a hodnotu b
ukazuje, je nezměněn. Je to proto, že jsme neukázali b
na a
sama o sobě. Ukázali jsme b
na hodnotu a
na kterou v té době ukazoval!
Rád si představuji proměnné jako malé šipky, které ukazují na místa v paměti počítače. Když tedy řekneme let a = 1
, říkáme:"Hej JavaScriptengine, chci, abys vytvořil místo v paměti s hodnotou 1
a poté vytvořte šipku (proměnnou) nazvanou a
které ukazuje na to místo v paměti."
Když pak řekneme:let b = a
, říkáme "Hej JavaScript engine, chci, abyste vytvořil šipku (proměnnou) s názvem b
který ukazuje na stejné místo jako a
ukazuje na v tuto chvíli."
Stejným způsobem, když voláte funkci, stroj JavaScript vytvoří novou proměnnou pro argumenty funkce. V našem případě jsme nazvali getLogger(fruit)
a JavaScript engine v podstatě udělal toto:
let arg = fruit
Takže když později uděláme fruit = 'peach'
, nemá to žádný vliv na arg
protože se jedná o zcela odlišné proměnné.
Ať už to považujete za omezení nebo funkci, faktem je, že to tak funguje. Pokud chcete udržovat dvě proměnné navzájem aktuální, existuje způsob, jak to udělat! No tak nějak. Myšlenka je tato:místo změny toho, kam šipky (proměnné) ukazují, můžete změnit to, na co ukazují! Například:
let a = {current: 1}
let b = a
console.log(a.current, b.current) // 1, 1
a.current = 2
console.log(a.current, b.current) // 2, 2 🎉
V tomto případě neměníme přiřazení a, ale spíše měníme hodnotu, která a
ukazuje na. A protože b
náhodou míří na stejnou věc, oba dostanou aktualizaci.
Aplikujme tedy toto řešení na náš logger
problém:
function getLatestLogger(argRef) {
function logger() {
console.log(argRef.current)
}
return logger
}
const fruitRef = {current: 'raspberry'}
const latestLogger = getLatestLogger(fruitRef)
latestLogger() // "raspberry"
fruitRef.current = 'peach'
latestLogger() // "peach" 🎉
Ref
přípona je zkratka pro "reference", což znamená, že hodnota, na kterou proměnná ukazuje, se jednoduše používá k odkazování na jinou hodnotu (což je v našem případě current
vlastnost objektu).
Závěr
S tím přirozeně existují kompromisy, ale jsem rád, že specifikace JavaScriptu vyžaduje, aby argumenty funkcí byly předávány hodnotou, nikoli odkazem. A řešení není příliš velké potíže, když to potřebujete (což je docela vzácné, protože proměnlivost ztěžuje normální pochopení programů). Doufám, že to pomůže! Hodně štěstí!