JS Objects:Rozptylování

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ěkujeme

Rád bych poděkoval následujícím úžasným vývojářům za jejich velkorysý č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"

V části 1 této série článků jsem se podrobně věnoval rozdílům mezi tím, co znamená tradiční definice „dědičnosti“ a jak JS [[Prototype]] mechanismus funguje. Viděli jsme, že JS funguje opačně než „dědičnost“ a je lépe označen jako „delegování chováním“. Pokud jste ji nečetli a máte o tomto prohlášení nějaké pochybnosti nebo zmatek, doporučuji vám, abyste si nejprve přečetli 1. část.

Dědičnost znamená do určité míry kopírování definice chování v řetězci, zatímco delegování chování znamená delegování chování směrem nahoru v řetězci. Nejde jen o sémantiku slova, ale o důležitý rozdíl, který při zkoumání může demystifikovat spoustu matoucích věcí kolem objektů JS.

Nejsem zdaleka první vývojář, který si tuto pravdu o JS uvědomil. Co se zde liší, je v mé reakci na toto zjištění. Odpovědí je obvykle vrstvení dalších konceptů, aby se vyhladily hrubé hrany nebo neočekávané důsledky toho, jak nás může překvapit „prototypální dědičnost“, abychom se pokusili vytvořit JS cítění spíše klasické OO.

Myslím, že tyto pokusy nás jen odvádějí od prosté pravdy o tom, jak JS funguje.

Raději bych identifikoval věci, které jsou pouze rušivými prvky, a dal je stranou a přijal pouze skutečnou podstatu toho, jak JS [[Prototype]] funguje. Než abych se snažil učinit JS více "dědičně přátelský", raději vytrhnu vše, co mě (a ostatní) mate, abych si myslel, že JS vůbec "dědičnost" má.

Typy

Často se uvádí, že pokud v JavaScriptu deklarujete funkci a přidáte věci do prototypu této funkce, pak to samo o sobě vytvoří definici vlastního „typu“, který lze instanciovat . Kdybychom byli v tradičním OO jazyce, takový způsob myšlení by mohl být vhodnější, ale tady v zemi JS je to jen jedno z mnoha rozptýlení.

Ve skutečnosti nevytváříte nový typ v žádném skutečném smyslu tohoto slova. Není to nový typ, který odhalí typeof a nebude to mít vliv na interní [[Class]] charakteristika hodnoty (která by se ve výchozím nastavení hlásila prostřednictvím Object#toString() ). Je pravda, že můžete provést určitou sebereflexi, abyste zkontrolovali, zda je objekt „instancí“ konstrukce nějaké funkce (prostřednictvím instanceof operátor). Ale co je důležité,foo1 instanceof Foo pouze následuje interní [[Prototype]] řetězec vašeho objektu foo1 abyste zjistili, zda na nějaké úrovni tohoto řetězce náhodou nenajde .prototype objekt připojený k Foo funkce.

Jinými slovy, reflexe, kterou děláte, není o kontrole, zda je hodnota zadaného typu, ani o konstruktoru funkce. Je to pouze o dotazu, zda je jeden objekt v [[Prototype]] jiného objektu řetěz. Název a sémantika instanceof operátor (s odkazem na „instance“ a „funkce konstruktoru“) vrství další a nepotřebný význam, což vás pouze mate, abyste si mysleli, že existuje něco víc než jednoduché [[Prototype]] probíhá kontrola řetězce.

Někteří vývojáři se mračí na použití instanceof , a proto se alternativní forma určování „typu“ nějakého objektu nazývá duck typing, což je v podstatě odvození „typu“ hodnoty tím, že se v objektu zkontroluje jeden nebo více charakteristických rysů, jako je konkrétní metoda nebo vlastnost.

Ať tak či onak, ve skutečnosti to nejsou "typy", jsou to pouze aproximace typů, což je jedna část toho, co dělá objektový mechanismus JS složitější než jiné jazyky.

Mixiny

Dalším rozptýlením je pokus napodobit automatické „kopírování“ dědičnosti pomocí vzoru „mixin“, který v podstatě ručně prochází všemi metodami/vlastnostmi na objektu a vytváří „kopii“ (technicky jen odkaz na funkce a objekty). na cílový objekt.

Neříkám, že mixiny jsou špatné – je to velmi užitečný vzor. Nicméně mixiny nemají nic společného s [[Prototype]] řetězu nebo dědění nebo delegování nebo cokoli z toho – spoléhají výhradně na implicitní přiřazení this tím, že má „vlastnící objekt“ v době volání funkce/metody. Ve skutečnosti zcela obcházejí [[Prototype]] řetěz.

Vezměte libovolné dva nezávislé objekty a nazvěte je AB (nemusí být propojeny pomocí [[Prototype]] vůbec) a stále můžete míchat A 's věci do B . Pokud tento styl kódu vyhovuje vaší situaci, použijte ho! Upozorňujeme však, že to nemá nic společného s [[Prototype]] nebo dědictví. Snažit se o nich přemýšlet jako o příbuzných je jen rozptýlení .

Dalším souvisejícím rozptýlením je, když se objeví nevyhnutelná touha vytvořit „vícenásobnou dědičnost“, protože JavaScript umožňuje, aby objekt byl pouze [[Prototype]] propojeno s jedním jiný objekt najednou. Když čteme o neexistenci vícenásobné dědičnosti v JavaScriptu, objeví se několik problémů a často jsou navržena různá „řešení“, ale nikdy je ve skutečnosti nevyřešíme, jen se více líbivě vzdáváme rukou, abychom odvedli pozornost od obtíží, které JS představuje. na syntaktické/sémantické úrovni.

Například v podstatě skončíte tím, že uděláte nějakou formu „míchání“, abyste do svého objektu přidali několik různých sad vlastností/metod, ale tyto techniky bez propracovaných a neefektivních obcházení elegantně nezvládnou kolize, pokud dva z vašich objekty "předchůdce" mají stejnou vlastnost nebo název metody. Na vašem objektu skončí pouze jedna verze vlastnosti/metody, a to je obvykle ta poslední, kterou jste přimíchali. Ve skutečnosti neexistuje čistý způsob, jak zajistit, aby váš objekt odkazoval na různé verze současně.

Někteří lidé si k vyřešení těchto problémů zvolí jiné rozptýlení pomocí vzoru „složení“. V podstatě místo kabeláže vašeho objektu C na obě AB , pouze udržujete samostatnou instanci každého z A aB uvnitř vašeho C vlastnosti/členy objektu. Opět, toto není špatný vzor, ​​má v sobě spoustu dobrých věcí.

Parazitická dědičnost je dalším příkladem vzoru, který řeší tento "problém", který [[Prototype]] nefunguje jako třídy jednoduše tím, že se vyhne [[Prototype]] celkem. Je to pěkný vzor, ​​ale myslím, že je to matoucí rozptýlení, protože díky němu cítíte jako byste dělali OO, když ne.

Ať už zde použijete jakoukoli techniku, v podstatě skončíte ignorováním [[Prototype]] řetězu a děláte věci ručně, což znamená, že jste se zcela vzdali mechanismu „prototypové dědičnosti“ JavaScriptu.

Polymorfismus

Jedním zvláštním rozptýlením, které končí vytvořením některých z nejnevhodnějších kódových vzorů, se kterými se v JS zabýváme, je polymorfismus, což je praxe, kdy máte stejnou metodu nebo název vlastnosti na různých úrovních vašeho „dědičného řetězce“ a poté pomocí super -style relativní odkazy pro přístup k verzím předchůdců téhož.

Problém je mechanický:JavaScript poskytuje this vlastnost, ale co je důležité, je vždy zakořeněna ve spodní části [[Prototype]] řetězec, ne na kterékoli úrovni řetězce, na které byla aktuální funkce nalezena. I když je pravda, že this.foobar() může skončit vyřešením (vyhledáním) foobar() na úrovni předka řetězce, uvnitř tohoto volání, jeho this bude stále původní zakořeněné this objekt.

Jednodušeji řečeno this není relativní, ale absolutní vzhledem k začátku zásobníku volání. Pokud měl JS super nebo currentThis (jak jsem nedávno navrhl), pak by tyto odkazy byly relativní k jakémukoli aktuálně vyřešenému odkazu v [[Prototype]] řetěz byl, což by vám umožnilo udělat relativní odkaz na odkaz "výše". JS však v současné době žádný takový mechanismus nemá. A this absolutní zakořenění z něj dělá neefektivní (nebo v nejlepším případě neefektivní, tedy nepraktické) řešení těchto relativních odkazů, které polymorfismus vyžaduje.

Většina pomocných knihoven OO se snaží poskytnout způsob, jak vytvořit super hovory, ale všichni nakonec musí dělat neefektivní věci pod pokličkou, aby takový relativní hovor fungoval.

třída { .. }

A konečně, myslím na dlouhé a vášnivě diskutované téma class { .. } syntaxe, která přichází do jazyka v ES6, představuje spíše pokus zakrýt, co JS skutečně dělá, tím, co si lidé přáli, aby JS dělal . Tyto druhy rozptýlení pravděpodobně nezlepší porozumění skutečným mechanismům JS. Někteří spekulují, že díky tomu bude JS přístupnější od tradičních OO oddaných, což může být zpočátku pravda, ale mám podezření, že konečným výsledkem je, že budou rychle frustrovaní z toho, jak to nefunguje, jak by očekávali.

Je důležité pochopit, že nová syntaxe třídy, kterou získáváme, nezavádí radikálně nové chování nebo klasičtější verzi dědičnosti. Jak JS [[Prototype]] končí delegování v současné době funguje v syntaxi a sémantice, které jsou předem načteny velkým množstvím zavazadel porozumění a očekávání, které jsou zcela v rozporu s tím, co ve skutečnosti získáte s třídami JSu . Pokud v současné době nerozumíte nebo se vám nelíbí „dědění“ objektu JS, class {..} Je nepravděpodobné, že by vás syntaxe uspokojila.

Ano, syntaxe odstraňuje některé standardní prvky explicitního přidávání položek do funkce "konstruktor" .prototype a Bůh ví, že všichni budeme rádi, když nebudeme muset zadávat function klíčové slovo tolikrát. Hurá! Pokud již plně rozumíte nepříjemným částem „tříd“ JS a nemůžete se dočkat class {..} abych osladil syntaxi, jsem si jistý, že jste spokojený, ale také si myslím, že jste pravděpodobně v menšině. Udělalo se příliš mnoho kompromisů na to, aby se vůbec dostal do jazyka, aby plně potěšil širokou škálu zcela opačných názorů.

Základní [[Prototype]] systém se nemění a téměř žádná z obtíží, které jsme právě nastínili, se měřitelně nezlepšují. Jedinou výjimkou je přidání super klíčové slovo. Předpokládám, že to bude vítaná změna.

I když jako okrajovou poznámku, modul ve skutečnosti nebude vázat super dynamicky (v době hovoru) na příslušný odkaz v [[Prototype]] řetěz, ale místo toho jej sváže staticky (v době definice) na základě vlastněného objektu odkazu na funkci. To možná vytvoří nějaké podivné WTF, protože engine bude muset za běhu vytvářet nové odkazy na funkce jako funkce, které používají super jsou přiřazeny různým vlastněným objektům. Je možné (nepotvrzené podezření), že nemusí fungovat ve všech případech, jak byste očekávali, pokud super byly místo toho vázány dynamicky.

Zjednodušení™

Právě jsme prozkoumali různé způsoby, kterými se mnoho vývojářů JS snaží navrstvit na další abstrakce a koncepty nad základní objektový mechanismus JS. Tvrdím, že je to chyba, která nás posouvá dále od krásy jádra JavaScriptu. Spíše než přidávat složitost k vyhlazení hrubých hran si myslím, že musíme věci odstranit, abychom se dostali k dobrým věcem.

V části 3 se přesně tomu budu věnovat a převedu JS ze složitějšího světa tříd a dědičnosti zpět do jednoduššího světa objektů a odkazů na delegování.