Přejít k navigační liště

Zdroják » JavaScript » JavaScript Restart – Neidentifikovatelný létající objekt

JavaScript Restart – Neidentifikovatelný létající objekt

Články JavaScript

Objektově orientované programování jest naším denním chlebem, živitelem i učitelem, leč možnosti JavaScriptu v tomto směru bývaly ubohé a ne každý vývojář se zamiloval do kouzel prototypové dědičnosti a přístupu dle hesla “Urob si sám”. Vše se však k lepšímu obrací, a to budiž tématem tohoto článku.

Nálepky:

Object.defineProperty()

Pro nastavování členských proměnných Javascriptového objektu (prostě vlastností neboli “proprt”) jsem se setkal s těmito přístupy:

  • přímý přístup k proměnné
    no ale fuj, hanba, to si říkáme programátoři? No, ale funguje to docela dobře.
  • Java přístup
    používáme metody getProperty a setProperty – což funguje bez ohledu na jazyk, vlastně není žádná zvláštní syntaxe, pouze jmenná konvence pro názvy metod
  • jQuery přístup
    pokud má metoda parametr, pak se chová jako setter jinak se chová jako getter(případně s jedním parametrem jako getter, se dvěma jako setter a tak dále)

    $(el).css(‘color’); //getter
    $(el).css(‘color’, ‘red’); //setter

Nemůžeme popřít, že práce s vlastnostmi objektu je trochu problém, který se řeší, a řeší se různými způsoby, a tak je na místě standardizace, kterou za nás udělá někdo jiný, a nám tak (doufejme) zčitelní kód.

Pojďme se tedy podívat na definici vlastnosti objektu v Javascriptu (ES 5.1):

Object.defineProperty(MyObject, ‘answerToTheUltimateQuestion’, {
  value: 42
});

To nám může posloužit maximálně k definici konstanty. Pokud se totiž zkusíte do konstanty zapsat:

MyObject.answerToTheUltimateQuestion = 10;

nedostanete žádné varování ani výjimku (nejsme-li ve strict módu), pouze její hodnota bude pořád 42.

Pokud použijeme parametr writable:

Object.defineProperty(MyObject, ‘answerToTheUltimateQuestion’, {
  value: 42,
  writable: true
});

pak se nám hodnota úspěšně uloží.

Zůstává pro mne poněkud záhadou, proč přednastavená hodnota pro writable je false, jelikož ve většině případů je třeba ji změnit.

Aby byla vlastnost viditelná při čtení vlastností objektu např. přes konstrukci for..in nebo přes Object.keys(), musíme nastavit ještě parametr enumerable (otestovat jej můžeme pomocí propertyIsEnumerable):

Object.defineProperty(MyObject, ‘answerToTheUltimateQuestion’, {
  value: 42,
  writable: true,
  enumerable: true
});

Záhada s přednastavenou hodnotou na false pokračuje. Nějak se mi nepodařilo přijít na případ, kdy bych potřeboval proměnnou, která je viditelná, jen pokud ji čtu přímo. Možná něco na způsob privátní proměnné.

Poslední vám (mnou) utajenou možností je parametr configurable:

Object.defineProperty(MyObject, ‘AnswerToTheUltimateQuestion’, {
  value: 42,
  writable: true,
  enumerable: true,
  configurable: true,
});

Configurable zařídí pouze takovou drobnost, povolí možnost vlastnost předefinovat nastavení vlastnosti nebo ji smazat pomocí příkazu delete.

Málem bych zapomněl na get a set, kvůli kterým tohle povídání vlastně vzniklo:

var answer,
  MyObject = {};

Object.defineProperty(MyObject, ‘answerToTheUltimateQuestion’, {
  get: function() {
    return answer;
  },
  set: function(newAnswer) {
    answer = newAnswer;
  },
  enumerable: true,
  configurable: true
});

Zcela logicky se blokuje writable nastavením set, zároveň není možno nastavit parametr writable, pokud existuje getter nebo setter.

Pro úplnost – výše zmíněné parametry se nazývají deskriptory a dělíme je na datové (set,get) a přístupové (configurable, writable, enumerable).

Object.defineProperties()

Je nutné si přiznat, že zápis pomocí defineProperty je celkem “ukecaný”, a pokud zadefinujete jen pár vlastností, zabere to strašně moc řádek. A proto vám jistě udělá radost možnost si usnadnit práci a definovat všechno vlastnosti pomocí jednoho objektu:

Object.defineProperties(Scalear.Neck.prototype, {
    namesVisible: {
        value: false,
        writable: true
    },
    rootNote: {
        value: 0,
        writable: true
    },
    scale: {
        value: 0,
        writable: true
    }
});

Object.create()

Každý, kdo chtěl dědit nejen po babičce, ale i po jiných objektech, se asi už potkal s takovouto konstrukcí:

Scalear.Box.prototype = new Mvc.View();

která má tu protivnou vlastnost, že nám zavolá konstruktor předka, a pokud ten bude, nedejbože, očekávat nějaké parametry, tak je zle.
Nyní lze dědičnost řešit takto:

Scalear.Box.prototype = Object.create(Mvc.View.prototype);

což ovšem není žádný zázrak (na počet znaků je to dokonce delší), ale konstruktor se nevolá a pokud přidáme druhý parametr:

Scalear.Box.prototype = Object.create(Mvc.View.prototype, {
  namesVisible: {
    value: false,
    writable: true
  },
  rootNote: {
    value: 0,
    writable: true
  },
  scale: {
    value: 0,
    writable: true
  }
});

který je, jak vidno, opět definice vlastností, stane se Object.create naší docela oblíbenou funkcí.

Vynechám-li vyšší výkonnost, pak už možnost definice vlastností zároveň s definicí dědičnosti je velký přínos pro zpřehlednění kódu. A zápis se zase o trochu víc podobá definici třídy.

Object.keys()

Když už jsem se v povídání o vlastnostech otřel o parametr enumerable, je na místě zmínit další funkci, která s enumerací přímo souvisí. Pokud chceme získat jména vlastností objektu, můžeme zavolat:

Object.keys(myObject)

a výsledkem je pole názvů vlastností, čili pak můžeme udělat toto:

Object.keys(myObject).forEach(function(key) {
    //kód
});

a máme variantu ke konstrukci for..in.

Zajímavé je, pokud jako parametr použijeme řetězec:

Object.keys(‘text’)

vrátí

[“0”, “1”, “2”, “3”]

v Chrome a Firefoxu, zatímco v IE 11 končí vyjímkou TypeError. Je to způsobenu změnou v ES6 (řetězec je iterable) proti chování ES5 (nebo spíše tím jak ten který prohlížeč pokročil v implementaci těchto novot), a tak bych si na řetězce raději dával pozor.

Object.freeze()

Během zkoumání toho, co objekty umí nového, jsem nalezl metodu freeze, nad jejímž použítím jsem si dlouho lámal hlavu. Nakonec jsem ji rád přidal do právě rozepsané aplikace. V souvislosti s touto metodou jsem narazil na anglické slovo “immutable”, což, jak jsem zjistil, není od základu “mute” čili “ztlumit”, alebrž nýbrž od “mutation” čili “změna” a “immutable” znamená tedy “neměnitelný”. A neměnitelnost je to, co právě tato metoda zařídí.

Máme-li objekt s daty, která potřebuje aplikace a která se při běhu aplikace nemají měnit (konfigurace, atd.) je možné jejich neměnnost právě pojistit metodou freeze.

var appInfo = {
  appName: ‘IceCream app’,
  version: ‘1.2’
};
Object.freeze(appInfo);

Pokud se nyní pokusíme v appInfo změnit některou hodnotu (nebo další přidat), příkaz se provede bez varování a výjimek (pokud nejsme ve sctrict módu), ale ke změně obsahu nedojde.

Zmrzlost (neměnnost) objektu se pak dá otestovat pomocí metody isFrozen:

Object.isFrozen(appInfo)

Zajímavé je, že pokud se pokusíte zavolat push nad “zmraženým” polem dostanete vyjímku, oznamující, že není možno měnit readonly vlastnost length. To je dobré vědět a myslet na to. Druhou věcí, na kterou je třeba nezapomenout, je, že k freeze není opačná metoda něco jako unfreeze nebo defrost, čili kdo jednou zmrazil, už nerozmrazí.

Object.seal()

Funkce Object.seal() je méně striktní variantou Object.freeze(). Znemožní přídávání nových vlastností objektu, ale umožní jejich editaci. Zároveň změní deskriptor configurable na false.

Seal znamená “utěsnit” nebo “uzavřít” což docela hezky charakterizuje, co funkce dělá. To, že objekt “utěsněný” se pak dá otestovat funkcí.

Object.isSealed(appInfo)

Object.preventExtensions()

Metoda Object.preventExtensions je méně striktní variantou Object.seal, ale tentokrát pouze znemožní přidání nové vlastnosti, ale nemění nic v destriptorech. otestuje se takto:

Nejsem si jist, kde bych použil Object.seal() a Object.preventExtensions(), ale bez nich by tento článek nebyl úplný. Rozhodně se tu otvírá prostor pro diskuzi.

Object.isExtensible(appInfo)

Jak vidno, tvůrcům a standartizátorům ECMA skriptu došly jazykové prostředky, a tudíž zvolili název, který nijak nenapovídá, že je tu jistá souvislost s předchozími dvěma funkcemi.

Object.freeze vs. Object.seal() vs. Object.preventExtensions()

Akce freeze seal preventExtensions
změna hodnoty zakázáno povoleno povoleno
změna deskriptoru zakázáno zakázáno povoleno
přidání vlastnosti zakázáno zakázáno zakázáno
Object.isSealed() true true false
Object.isFrozen() true false false
Object.isExtensible() false false false

Object.observe()

Zatím všechny všechny funkce, o kterých tu byla řeč, patří do ES5. Object.observe() je součástí specifikace ES6. To znamená, že kromě Chrome, ho ještě žádný prohlížeč neimplementoval. Object.observe() je ovšem dle mého názoru naprosto bezvadná záležitost, kvůli které stojí za to použít polyfill.

Ve Firefoxu (Gecko) existuje ještě podobná funkce Object.watch(), která je ovšem nestandardní a není doporučeno ji využívat. Je třeba dávat pozor na jejich záměnu, často se to splete.

Představte si situaci – na straně klienta máme uložená data v modelu a několik view, která se musí aktualizovat, jakmile se změní model. Jak detekovat změnu modelu? Samozřejmě knihovny jako AngularJS, Ember.js a Ext JS (se kterými mám bližší zkušenost), tohle řeší a vlastně to byl jeden z motivů jejich vzniku. Nebo si to může vývojář napsat sám, jenže kdo chce pořád vymýšlet kolo, že?

Nuže, nyní máme funkci, které předáme náš model (objekt) a callback funkci. Callback funkce se zavolá při každé změně modelu, a dostane seznam změn jako parametr.

Object.observe(model, function(changes){
    changes.forEach(function(change) {
        //kód
    });
});

Co přesně přijde v seznamu změn? Pole takovýchto objektů:

{
  name: “scale”  //jméno proměnné
  object: Object //objekt po změně
  oldValue: 13   //stará hodnota
  type: “update” //type změny
}

Pak už jen zbývá dopsat kód, který bude řešit co se bude dít při změně té které proměnné modelu a máme hotovo.

Chrome Object.observe() již implementoval a pro ostatní prohlížeče lze použít polyfill.

V souvislostí s Object.observe() se začíná mluvit o revoluci v Javascriptu a vývojáři-vizionáři se začínají se slzami v očích loučit se svými oblíbenými frameworky a knihovnami.

Perlička na závěr

Na závěr bych se rád vrátil na začátek. Pokud chceme definovat vlastnost můžeme použít následující syntaxi (která je součástí ES 5.1):

var answer;

get answerToTheUltimateQuestion() {
      return answer;
}

set answerToTheUltimateQuestion(newAnswer) {
  answer = newAnswer;
}

což jistě potěší vývojáře používajících k obživě a radosti i jiné jazyky (zejména C#), jenže jak vidno, je to zpětně nekompatibilní syntaxe, čili ve starších prohlížečích skript prostě nepoběží, a žádný polyfill to nespraví (ani nemůže).

Komentáře

Subscribe
Upozornit na
guest
9 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Marek Sirkovský

Zase ty uvozovky:)

Ondřej Žára

Taky jsou tam koukám nějaké jímky…

karfcz

Object.observe není součástí ES6 (resp. ECMAScriptu 2015).
No a myslím, že o tom vizionáři nemluví ani tak jako o revoluci, ale spíše jako o DOA či nejzbytečnější feature ES7.

Pavel Lang

Souhlas, Object.observe je antipattern.

Jarda

Mohl bys to prosim trochu rozvest? Proc je to antipattern? Co je na tom spatneho?

Ondřej Žára

Protože chlapácká tvrzení „X je antipattern“ či „Y je budoucnost“ dnes frčí :-)

https://twitter.com/DrCraniax/status/384210747963625472

Martin Hassman

Většinou to znamená zhruba: „Mně se X (ne)líbí a musí/nesmí se líbit i vám, pro což jsem schopen uvést dlouhý seznam argumentů.“ 8-)

tacoberu

Obvykle je observer považován za antipatern protože tam naprosto nekontrolovatelně poslouchá každý každého. Což mívá negativní dopad na výkon a přehlednost. Nehledě na riziko zaciklení.

O dost lepší vzor je monitor, který je podobně jako observer, akorád je tam ústřední prvek, kde se všechny registrace můžou kontrolovat.

karfcz

Pavel Lang výše mi tak trochu podsunul něco, co jsem tvrdit nechtěl. Já nepovažuju observer obecně za antipattern. Ale Object.observe protlačili do specifikace nadšenci kolem Angularu, protože se jim to hodilo do krámu kvůli výkonnostním a jiným problémům s dirty checkingem. Mezitím se ovšem začaly prosazovat odlišné přístupy, zejména ve spojení s immutabilními datovými strukturami, čímž se Object.observe stává zbytečností a hrozí, že až, resp. jestli vůbec, bude implementována na všech platformách, už to nikdo nebude potřebovat.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.

Pocta C64

Za prvopočátek své programátorské kariéry vděčím počítači Commodore 64. Tehdy jsem genialitu návrhu nemohl docenit. Dnes dokážu lehce nahlédnout pod pokličku. Chtěl bych se o to s vámi podělit a vzdát mu hold.