JS Objects:Zdědil nepořádek

Objekty JS:TL;DR

JavaScript byl od začátku sužován nedorozuměním a neohrabaností kolem svého systému „prototypové dědičnosti“, většinou kvůli skutečnosti, že „dědičnost“ vůbec není způsob, jakým JS funguje, a snaha o to vede pouze k nedorozuměním a zmatkům. musí vydláždit pomocnými knihovnami uživatelské země. Místo toho přijetí toho, že JS má „delegování chování“ (pouze delegování odkazů mezi objekty), přirozeně zapadá do toho, jak funguje syntaxe JS, což vytváří rozumnější kód bez potřeby pomocníků.

Když dáte stranou rušivé prvky, jako jsou mixiny, polymorfismus, kompozice, třídy, konstruktory a instance, a soustředíte se pouze na objekty, které na sebe navazují, získáte mocný nástroj v delegování chování, o kterém se snadněji píše, uvažuje, vysvětluje, a udržovat kód. Jednodušší je lepší. JS je "pouze objekty" (OO). Nechte výuku těm jiným jazykům!

Děkuji

Rád bych poděkoval následujícím úžasným vývojářům za jejich štědrý čas při zpětné vazbě/technické recenzi této série článků:David Bruant, Hugh Wood, Mark Trostler a Mark McDonnell. Jsem také poctěn, že David Walsh chtěl publikovat tyto články na svém fantastickém blogu.

Úplná série

  • Část 1:Objekty JS:Zděděný nepořádek
  • Část 2:Objekty JS:Rozptylování
  • Část 3:Objekty JS:De"konstrukce"

Jak smutná kritika na JS je ten citát, je to docela pravda. (Nemám žádný pohled na Haskell nebo Monads, takže mluvím pouze o JS a dědičné části). Ze všech matoucích a v závislosti na vašich předsudcích „špatných“ částí tohoto jazyka JS chování this[[Prototype]] řetězec zůstaly jedněmi z nejvíce nepolapitelných na vysvětlení a přesné použití.

Trochu na pozadí jsem vyvíjel JS na plný úvazek od roku 2007. Prvním velkým zjevením, které jsem tehdy měl, bylo pochopení toho, jak uzávěry fungují a jak umožňují klasický vzor modulů. První open source projekt, který jsem napsal (začátek roku 2008), byl flXHR, mezidoménové Ajaxové prollyfill využívající standardní rozhraní Ajax (XHR) (prostřednictvím skrytého prvku flash), které se silně spoléhalo na vzor modulu.

Je to dost možná moje "ah-ha!" moment kolem vzoru modulu, který mě uspokojil natolik, že jsem nikdy necítil silnou potřebu použít vzor „dědičnosti“ na můj návrh JS.

Nicméně, stejně jako většina vývojářů JS, jsem za ta léta přečetl spoustu blogů a knih, které se snažily (a většinou neúspěšně) vysvětlit přitažlivost a záhadu, kterou je „dědění JavaScriptu“ (neboli „prototypální dědičnost“).

Ale pokud je to tak těžké pochopit a ještě těžší to skutečně udělat správně, bod přesto mi uniká. A zjevně nejsem v této frustraci sám.

OO v JavaScriptu

V tradičních objektově orientovaných jazycích odpovídá syntaxe tříd sémantice. Objektově orientované koncepty tříd, dědičnosti a polymorfismu můžete vyjádřit přímo a explicitně pomocí syntaxe jazyka. Není třeba používat nějakou pomocnou knihovnu, abyste se dostali do chování podobného OO pomocí obcházení jiných jazykových prostředků.

JavaScript na druhé straně má sadu syntaxe, která vypadá poněkud nestandardně, ale chová se frustrujícím způsobem odlišným způsobem (kterým se budeme věnovat v této sérii článků). Výsledkem je, že běžný způsob, jak implementovat OO vzory v JS, je prostřednictvím kterékoli z různých pomocných knihoven uživatelského prostředí, které vám umožňují vyjádřit požadované sémantické vztahy mezi vašimi „objekty“. Důvod, proč je většina vývojářů JS používá, je ten, že základní syntaxe JS činí tyto sémantické výrazy nepohodlnými. Je hezké nechat knihovnu, aby se vyrovnala s matoucími škytavkami v syntaxi.

Knihovny jako jQuery jsou užitečné, protože skrývají ošklivé detaily řešení rozdílů mezi různými prohlížeči v JS engine . Tyto pomocné knihovny OO jsou však jiné:jdou do značné míry, aby skryly skutečnou povahu mechanismů OO JavaScriptu , místo toho je maskuje sadou vzorů, které jsou známější v jiných jazycích.

V tomto bodě porozumění bychom si měli položit otázku:je obtížnost vyjadřování tříd a dědičnosti v čistém JavaScriptu selhání jazyka (které lze dočasně vyřešit pomocí uživatelských knihoven a nakonec vyřešit přidáním k jazyku, jako je class { .. } syntaxe), jak to cítí mnoho vývojářů, nebo je to něco hlubšího? Svědčí to o zásadnějším nepoměru, že se snažíme v JS udělat něco, k čemu to prostě není zamýšleno?

Ne každý pil lekce JS kool-aid, takže zbytek této série článků bude upřednostňovat jiný pohled.

Plán

Jednou z nejběžnějších metafor používaných v tradiční třídě/dědičnosti OO je, že třída představuje „plán“ pro dům, který má být postaven, ale jakmile vytvoříte instanci této třídy, v podstatě zkopírujete všechny charakteristiky z plánu do skutečného postaveného domu. Dům. Tato metafora se do určité míry částečně shoduje s tím, co se ve skutečnosti děje na jazykové úrovni, když je kód kompilován, v tom, že jaksi zplošťuje definici hierarchie dědičnosti třídy (bez „virtuálních“ metod) do instance.

Hlavním pilířem kódování orientovaného na dědičnost je samozřejmě přepisování a polymorfismus, který objektu umožňujeautomaticky získat přístup k definici metody s nejvíce potomky, ale také použít super -style relativních odkazů na přístupové předchůdce (také „virtuální“) verze stejnojmenného metoda. V těchto případech kompilátor udržuje vyhledávací tabulky pro virtuální metody, ale srovnává nevirtuální části definice třídy/dědičnosti. Kompilátor může určit hodně o tom, co je třeba zachovat a ne, a vysoce optimalizovat definiční strukturu, kterou vytváří v kompilovaném kódu.

Pro naše účely si můžeme tradiční třídní dědičnost představit jako v podstatě zplošťující „kopii“ chování v řetězci až po instanci. Zde je diagram ilustrující vztah dědičnosti mezi nadřazenou/základní třídouFoo a podřízená třída Bar a poté instance každého, respektive pojmenované foo1foo2bar1 abar2 . Vizuálně ukazují šipky (také znám jako „kopírování“) zleva doprava a shora dolů:

Co je v názvu?

Navzdory vypůjčeným důsledkům běžného názvu „prototypální dědičnost“ funguje mechanismus JavaScriptu zcela jinak, což uvidíme za chvíli.

Jak definitivně ("...charakteristiky přenášené z rodiče na potomka"), tak i behaviorálně (jak je popsáno výše), je "dědičnost" nejtěsněji spojena s myšlenkou "kopírování" z rodiče na dítě.

Když pak vezmete „dědičnost“ a použijete jej na mechanismus, který má velmi odlišné chování, ptáte se na zmatek, který sužuje „dědičnost JavaScriptu“ dokumentaci, vzdělávání a používání již téměř 2 desetiletí.

Abychom se pokusili prokousat se tímto nepořádkem, nechme stranou označení „dědičnost“ a jeho důsledky pro JS a doufejme, že se nám podaří dospět k něčemu, co je koncepčně přesnější a funkčně užitečnější.

A.B.D's:Vždy delegujte

Mechanismus vlastností typu OO pro objekty v JavaScriptu je označen [[Prototype]] , což je vnitřní charakteristika jakéhokoli objektu nazval svůj prototyp-chain -- speciální odkaz na jiný objekt. Je to něco jako mechanismus rozsahu v tom, že [[Prototype]] linkage popisuje, na který alternativní objekt by se mělo odkazovat, pokud požadujete vlastnost nebo metodu pro váš objekt, který neexistuje.

Jinými slovy, označujete objekt, který má být delegován chování, pokud toto chování není pro daný objekt definováno.

Výše uvedená třída FooBar příklad, vyjádřený v JS, souvisí s objektem Bar.prototype na Foo.prototype a poté foo1foo2bar1bar2 objektů na jejich příslušné [[Prototype]] s. Šipky (nikoli kopie, ale živé odkazy) ukazují v JS způsobem zprava doleva a zdola nahoru:

„Delegování chování“ je přesnější výraz pro popis [[Prototype]] JavaScriptu . To není jen otázka sémantiky slova, je to zásadně odlišný typ funkčnosti.

Pokud se pokusíte ilustrovat delegování chování z hlediska metafory „návrhu“, rychle uvidíte, jak se totálně zhroutí. Neexistuje žádný způsob, jak by můj domov, postrádající pokoj pro hosty, mohl jednoduše odkazovat na jiný dům nebo na původní plány, aby poskytl ložnici pro mou tchyni, když přijde na návštěvu. I když výsledky, kterých můžete dosáhnout, mají nějaké příslušné podobnosti, pojmy „dědičnost“ a „delegování chování“ jsou zcela odlišné .

Někteří vývojáři trvají na tom, že „delegování“ je pouze dynamická verze „dědění“, jako dvě strany téže mince, ale já je vidím jako ortagonální systémy .

Jak delegovat?

K tomu se vrátíme později v sérii článků, ale Object.create(..) byl přidán do ES5, aby pomohl s vytvořením objektu a poté volitelně propojil jeho [[Prototype]] na jiný objekt. Vytvořený odkaz je delegační odkaz, nikoli dědění po kopii.

Poznámka: Jakmile má objekt své [[Prototype]] řetěz nastaven při jeho vytvoření, měl by z větší části být považován za vytesaný do kamene a neměnný. Technicky vzato, prohlížeče, které podporují __proto__ vlastnost, veřejná reprezentace interního odkazu, vám umožní kdykoli změnit, kde je objekt propojen. Tato praxe je však poseta nášlapnými minami a obecně je odsuzována – je to téměř jistě něco, čemu byste sevyhnuli ve vašem kódu.

Píkové rány

Viděli jste, jak se mechanismy v JavaScriptu srovnatelně liší od mechanismů v jiných jazycích. Ale je v pořádku se těchto rozdílů pouze zříci, abychom mohli nadále používat termín „dědičnost“ pro JS?

Faktem je, že to prostě není přesné použití termínu. Tím, že trváme na tom, že JavaScript má „dědičnost“, ve skutečnosti říkáme, že na významu slova „dědičnost“ nezáleží, nebo je spíše měkký.

JS staticky neanalyzuje, které části dědičného řetězce může bezpečně vyrovnat a zkopírovat , udržuje odkazy na celý řetězec delegování během běhu, jako samostatné objekty , což znamená, že náš kód může využívat různé výkonné dynamické vzory „pozdní vazby“.

Pokud se budeme neustále snažit napodobovat dědičnost v JavaScriptu (zatraceně s překážkami v syntaxi), dostaneme se vyrušenipřijdete o všechnu tu sílu který byl do našeho jazyka zabudován od začátku .

Říkám:nazvěme to, jak to je, a přestaňme se snažit vršit do JavaScriptu tyto další koncepty, které označení „dědičnost“ implikuje.

Tak co?

Doposud jsem se snažil identifikovat některé mylné představy o [[Prototype]] JS mechanismus a jak „dědičnost“ není užitečné označení.

Stále můžete být skeptičtí, proč ve skutečnosti záleží na tom, jak tento mechanismus podobný OO v JS nazýváme? V další části série článků se budu zabývat mnoha nástrahami tradičního programování „založeného na třídách“, o kterých si myslím, že jsou rušivými prvky, které nás vedou k přijetí podstaty toho, jak objekty JS spolupracují. Ve skutečnosti bychom dokonce mohli říci, že třídy/dědičnost jsou předčasnou optimalizací pro JavaScript.

Odstranění těchto rušivých vlivů nás vede k části 3, kde uvidíme jednodušší a robustnější vzor pro náš kód JS, a co je důležitější, náš kód bude skutečně odpovídat naší sémantice bez musíme skákat přes obruče, abychom skryli ošklivé neshody.

Těšte se na díly 2 a 3 později tento týden!