Myšlení JavaScript

Onehdy jsem učil JavaScriptový workshop a jeden z účastníků se mě během polední přestávky zeptal na hlavolam JS, který mě opravdu přivedl k zamyšlení. Tvrdil, že na něj narazil náhodou, ale jsem trochu skeptický; mohl to být jen záměrný WTF trik!

Každopádně jsem se spletl hned několikrát, když jsem se to pokoušel analyzovat, a musel jsem kód spustit analyzátorem a poté konzultovat specifikaci (a JS guru!), abych zjistil, co se děje. Protože jsem se během procesu naučil nějaké věci, řekl jsem si, že se o to s vámi podělím.

Ne, že bych čekal, že budete někdy úmyslně psát (nebo číst, doufejme!) kód jako je tento, ale umět myslet spíše jako JavaScript vám vždy pomůže napsat lepší kód.

Nastavení

Otázka, která mi byla položena, byla tato:proč tento první řádek "funguje" (kompiluje/spouští), ale druhý řádek zobrazuje chybu?

[[]][0]++;

[]++;

Myšlenka za touto hádankou je, že [[]][0] by měl být stejný jako [] , takže buď by měly fungovat obě, nebo by obě měly selhat.

Moje první odpověď, po chvíli přemýšlení, byla, že oba by měly selhat, ale z různých důvodů. V několika účtech jsem se mýlil. Ve skutečnosti platí první (i když docela hloupé).

Mýlil jsem se, přestože jsem se snažil přemýšlet jako JavaScript. Bohužel se mi zvrtlo myšlení. Bez ohledu na to, kolik toho víte , stále si můžete snadno uvědomit, že nevíte věci.

To je přesně důvod, proč vyzývám lidi, aby přiznali:"Neznáš JS"; nikdo z nás nikdy plně neví něco tak složitého jako programovací jazyk jako JS. Učíme se některé části a pak se učíme více a stále se učíme. Je to věčný proces, nikoli cíl.

Moje chyby

Nejprve jsem viděl dva ++ použití operátorů a můj instinkt byl, že obojí selže, protože unární přípona ++ , jako v x++ , je většinou ekvivalentní x = x + 1 , což znamená x (ať už je to cokoli) musí být platné jako něco, co se může zobrazit na levé straně = úkol.

Ta poslední část ve skutečnosti je pravda, v tom jsem měl pravdu, ale ze špatných důvodů.

Špatně jsem si myslel, že x++ je něco jako x = x + 1; v tomto myšlení []++ je [] = [] + 1 by bylo neplatné. I když to rozhodně vypadá divně, je to vlastně docela v pořádku. V ES6, [] = .. část je platnou destrukcí pole.

Myslím na x++ jako x = x + 1 je zavádějící, líné myšlení a neměl bych se divit, že mě to svedlo z omylu.

Navíc jsem o prvním řádku myslel také špatně. Myslel jsem, že [[]] vytváří pole (vnější [ ] ) a poté vnitřní [] se snaží být přístupem k vlastnostem, což znamená, že je stringifikovaný (na "" ), takže je to jako [""] . To je nesmysl. Nevím, proč se mi tady pokazil mozek.

Samozřejmě pro vnější [ ] být pole ke kterému se přistupuje , muselo by to být jako x[[]] kde x je věc, ke které se přistupuje, nejen [[]] sama o sobě. Každopádně přemýšlím špatně. Jsem hloupý.

Opravené myšlení

Začněme nejjednodušší opravou myšlení. Proč je []++ neplatný?

Abychom získali skutečnou odpověď, měli bychom jít k oficiálnímu zdroji autority na taková témata, spec!

Řečeno konkrétně, ++ v x++ je typ "Update Expression" nazývaný "Postfix Increment Operator". Vyžaduje x část je platným „výrazem na levé straně“ – ve volném slova smyslu výrazem, který je platný na levé straně = . Ve skutečnosti přesnější způsob, jak si to představit, není levá strana = , ale spíše platný cíl úkolu.

Když se podíváme na seznam platných výrazů, které mohou být cílem úkolu, vidíme mimo jiné věci jako „Primární výraz“ a „Výraz člena“.

Pokud se podíváte na Primární výraz , zjistíte, že "Array Literal" (jako naše [] !) je platný, alespoň z hlediska syntaxe.

Tak počkej! [] může být výraz na levé straně, a je tedy platný pro zobrazení vedle ++ . Hmmm. Proč tedy []++ dát chybu?

Možná vám uniklo, což jsem udělal já, je:není to SyntaxError vůbec! Jde o běhovou chybu s názvem ReferenceError .

Občas se mě lidé ptají na další matoucí – a zcela související! – výsledkem je JS, že tento kód je platnou syntaxí (ale stále selže za běhu):

2 = 3;

Je zřejmé, že číselný literál by neměl být něčím, co můžeme přiřadit. To nedává smysl.

Ale není to neplatná syntaxe. Je to jen neplatná runtime logika.

Jaká část specifikace tedy tvoří 2 = 3 selhat? Ze stejného důvodu jako 2 = 3 selhání je stejný důvod jako []++ selže.

Obě tyto operace používají abstraktní algoritmus ve specifikaci nazvané "PutValue". Krok 3 tohoto algoritmu říká:

Reference je speciální typ specifikace, který odkazuje na jakýkoli druh výrazu, který představuje oblast v paměti, kde lze přiřadit nějakou hodnotu. Jinými slovy, abyste byli platným cílem, musíte být Reference .

Jednoznačně 2 a [] nejsou Reference s, takže to je důvod, proč za běhu získáte ReferenceError; nejsou platnými cíli přiřazení.

Ale co...?

Nebojte, nezapomněl jsem na první řádek úryvku, který funguje. Pamatujte, že moje myšlenky byly celé špatné, takže musím udělat nějaké opravy.

[[]] sám o sobě není vůbec přístupem k poli. Je to jen hodnota pole, která náhodou obsahuje jinou hodnotu pole jako svůj jediný obsah. Představte si to takto:

var a = [];
var b = [a];

b;  // [[]]

Vidíte?

Takže teď, [[]][0] , o co jde? Pojďme to znovu rozebrat pomocí některých dočasných proměnných.

var a = [];
var b = [a];

var c = b[0];
c;  // [] -- aka, `a`!

Takže původní předpoklad nastavení je opravit. [[]][0] je v podstatě stejný jako jen [] sám.

Zpět k původní otázce:proč tedy řádek 1 funguje, ale řádek 2 ne?

Jak jsme si všimli dříve, "Update Expression" vyžaduje "LeftHandSideExpression". Jedním z platných typů těchto výrazů je "Member Expression", například [0] v x[0] – to je výraz člena!

Vypadat povědomě? [[]][0] je člen výraz.

Takže jsme dobří v syntaxi. [[]][0]++ je platný.

Ale počkej! Počkejte! Počkejte!

Pokud [] není Reference , jak by mohl [[]][0] – výsledkem je pouze [] , zapamatovat si! – může být považován za Reference takže PutValue(..) (popsáno výše) nevyvolá chybu?

Tady jsou věci trochu složitější. Kloboukový tip mému příteli Allen-Wirfs Brockovi, bývalému redaktorovi specifikace JS, za to, že mi pomohl spojit si věci.

Výsledkem členského výrazu není samotná hodnota ([] ), ale spíše Reference na tuto hodnotu – viz krok 8 zde. Takže ve skutečnosti [0] přístup nám poskytuje referenci na 0. pozici tohoto vnějšího pole, místo aby nám poskytl skutečnou hodnotu v této pozici.

A proto je platné použít [[]][0] jako levý výraz:ve skutečnosti je to Reference přece!

Ve skutečnosti, ++ skutečně aktualizuje hodnotu, jak můžeme vidět, pokud bychom měli tyto hodnoty zachytit a zkontrolovat je později:

var a = [[]];
a[0]++;

a;  // [1]

a[0] členský výraz nám dává [] pole a ++ jako matematický výraz jej donutí k primitivnímu číslu, které je první "" a poté 0 . ++ pak to zvýší na 1 a přiřadí jej a[0] . Je to jako a[0]++ byl ve skutečnosti a[0] = a[0] + 1 .

Malá poznámka na okraj:pokud spustíte [[]][0]++ v konzoli vašeho prohlížeče bude hlásit 0 , nikoli 1 nebo [1] . Proč?

Protože ++ vrátí „původní“ hodnotu (no, po vynucení – viz krok 2 a 5 zde), nikoli aktualizovanou hodnotu. Takže 0 se vrátí a 1 se vloží do pole prostřednictvím Reference .

Samozřejmě, pokud neuchováte vnější pole v proměnné jako my, je tato aktualizace diskutabilní, protože samotná hodnota zmizí. Ale bylo nicméně aktualizováno. Reference . Skvělé!

Dodatečně opraveno

Nevím, jestli oceňujete JS nebo se cítíte frustrovaní všemi těmito nuancemi. Ale tato snaha mě nutí více respektovat jazyk a obnovuje moji sílu, abych se ho učil ještě hlouběji. Myslím, že jakýkoli programovací jazyk bude mít svá zákoutí, z nichž některá máme rádi a některá nás přivádějí k šílenství!

Bez ohledu na vaše přesvědčení by neměly existovat žádné neshody v tom, že bez ohledu na to, jaký nástroj si vyberete, myšlení spíše jako tento nástroj vám pomůže tento nástroj lépe používat. Veselé myšlení JavaScriptu!