JavaScript Pass By Value Funkční parametry

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í!