4. diel - Objektovo orientované programovanie v CoffeeScript
Zdravím vás u štvrtého dielu seriálu, ktorý sa zaoberá syntaxou jazyka CoffeeScript. V tomto dieli preberieme objektovo orientované programovanie. Najprv si pripomenieme prototypy z JavaScriptu a potom sa pozrieme na OOP s triedami, ako ho poznáte z väčšiny C-like jazykov.
Ako to poznáme z JavaScriptu
Vieme, že JS využíva objektovo orientované programovanie na princípe prototypov. Namiesto tried a objektov, ktoré sú ich inštancií, stojí tento spôsob na princípe klonovanie už existujúceho objektu, ktorý slúži ako prototyp. Vytvorme si jednoduchý príklad s autom:
// konstruktor var Auto = function(znacka, model, barva) { this.znacka = znacka; this.model = model; this.barva = barva; this.palivo = 100; }; // přidání funkce do prototypu Auto.prototype.jed = function() { if(this.palivo >= 10) { this.palivo -= 10; alert('Jsem ' + this.znacka + ' ' + this.model + ' a jeduuuuuuuu...'); } else { alert('Došlo palivo.'); } }; var sportak = new Auto('Mitsubishi', 'Lancer Evolution IX', 'stříbrná'); sportak.jed();
Asi by sme z minulých dielov vedeli, ako tento kód napísať v CoffeeScriptu. Vyzeral by nejako takto:
Auto = (znacka, model, barva) -> @.znacka = znacka @model = model @.barva = barva @palivo = 100 Auto::jed = -> if @.palivo >= 10 @palivo -= 10 alert "Jsem #{@.znacka} #{@model} a jeduuuuuuuu..." else alert 'Došlo palivo.' sportak = new Auto 'Mitsubishi', 'Lancer Evolution IX', 'stříbrná' do sportak.jed
Dve dvojbodky v Auto::jed sú skratkou pre
.prototype. a slovo this nahradzuje zavináč.
Bystrejší z vás si všimli, že niekedy dávam medzi zavináč a názov
vlastnosti bodku, a niekedy nie. Dôvod je jednoduchý - chcem vám ukázať,
že je to úplne jedno.
Znak
@ nahrádza samotné slovo this aj this.
podľa situácie, môžete si vybrať, čo je vám milšie, ja preferujem verziu
bez bodky. Dve bodky a viac by vám samozrejme spôsobili chybu.
K prototypovému prístupu v CoffeeScriptu je to vlastne všetko, práve teraz vás však čaká ...
Hlavný chod
Pre tých, čo sa strácajú v prototypoch, nepáči sa im tento spôsob OOP,
alebo proste len preferujú spôsob známy z C ++, C #, Javy a "asi milióna"
ďalších jazykov, CoffeeScript ponúka triedy, písané (ako inak než) slovom
class. Vieme, že CoffeeScript je JavaScript vnútri teda stále
bije prototypové srdce, je to len abstrakcie, ktorá nám však dovoľuje
používať známe postupy a hlavne uľahčí prácu ľuďom, pre ktorých je
dedičnosť v JS španielskou dedinou. Samozrejme nie je problém miešať tieto
dva prístupy dokopy.
Prepíšme si nás kedysi príklad s použitím triedy:
class Auto constructor: (@znacka, @model, @barva) -> jed: -> if @palivo >= 10 @palivo -= 10 alert "Jsem #{@znacka} #{@model} a jeduuuuuuuu..." else alert 'Došlo palivo.'
Tento zápis triedy by nemal nikoho prekvapiť. Konštruktor označujeme
slovom constructor. Toto slovo však nie je kľúčovým slovom
CoffeeScriptu a preto môžete mať premennú pomenovanú "constructor" (z
pochopiteľných dôvodov to neodporúčam). Slovo zmenia svoje správanie len
vtedy, keď sa nachádza v triede. Kam ale zmizlo telo konstruktoru? Všimnite
si @ v parametroch funkcie. Hovoríme tým funkciu, nech parametre
dosadí do vlastností triedy s rovnakým menom, ako možno vidieť v
JavaScripte:
var Auto; Auto = (function() { // náš 'constructor' function Auto(znacka, model, barva) { this.znacka = znacka; this.model = model; this.barva = barva; } // přidání metody do 'třídy' Auto.prototype.jed = function() { if (this.palivo >= 10) { this.palivo -= 10; return alert("Jsem " + this.znacka + " " + this.model + " a jeduuuuuuuu..."); } else { return alert('Došlo palivo.'); } }; return Auto; })();
Zápis v CoffeeScriptu nás ušetrí nutnosti písať rovnaký názov 3x miesto raz a niekoľko riadkov navyše. Dôvodom pre uzavretie "triedy" do zátvoriek je, ako už to tak býva, Internet Explorer.
Dedičnosť
Vieme, že bez dedičnosti by to nebolo ono, a aj keď ju JavaScript obsahuje, je obzvlášť pre začiatočníkov krkolomná. Vďaka CoffeeScriptu ju však môžeme zapisovať a využívať veľmi ľahko. Tu je príklad:
class Vozidlo constructor: (@pocetKol) -> class Motocykl extends Vozidlo constructor: -> super 2
Dedičnosť sa zapisuje pomocou slová extends, ako v PHP, Jave
atď. Keď chceme volať funkciu z predka s rovnakým menom, použijeme slovo
super (v našom prípade v konstruktoru). Pozrieme sa, čo nám
bolo vygenerované:
// 1. var Motocykl, Vozidlo, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { // 2. for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } // 3. function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); // 4. child.__super__ = parent.prototype; return child; }; Vozidlo = (function() { function Vozidlo(pocetKol) { this.pocetKol = pocetKol; } return Vozidlo; })(); // 5. Motocykl = (function(_super) { __extends(Motocykl, _super); function Motocykl() { Motocykl.__super__.constructor.call(this, 2); } return Motocykl; })(Vozidlo);
Konečne niečo, čo vyzerá zaujímavo. Rozoberme si celý kód:
- Explicitné hoisting, ten už poznáme. Druhý riadok deklaruje iba skratku
pre metódu
hasOwnProperty, ktorá je použitá v nasledujúcej funkcii (prečo nie je táto metóda deklarovaná vo vnútri, alebo nie je používaná rovno, je mi záhadou) - Prvá časť tela funkcie
__extendsnakopíruje vlastnosti predka do potomka (aj ich hodnoty!) - Zabezpečia, že objekt
child.prototypemá ako prototyp inštanciu predka. Tiež sa postará o to, že všetky inštancie potomka majú správny konštruktor. Táto časť je ekvivalentom ECMAScript 5 kódu:
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
- Pridá vlastnosť
__super__, do ktorej uložia referenciu na předkův prototyp. Toho sa využíva práve vo volanísuperfunkcie v CoffeeScriptu. Bez tohto riešenia by sasupermuselo riešiť volaním predka jeho mene, čo môže spôsobiť problémy. - V deklarácii 'triedy' je odovzdaný predok ako parameter - v tomto prípade
Vozidlo. Na prvom riadku v tele funkcie sa zavolá skôr vytvorená funkcie na
"prepojenie". V konstruktoru je použitá vlastnosť
__super__spoločne s metódoucall(), ktorá zavolá metódu predka vo svojom kontexte.
Snáď ste teraz pochopili, čo sa "pod kapotou" deje. Ak nie, nevadí,
život je dlhý, pochopíte to inokedy.
To by bolo k dedičnosti všetko, CoffeeScript neobsahuje
mnohonásobnou dedičnosť (JS ju tiež nemá, je potreba použiť mixin), ani
interface (nemá kontrolu dátových typov počas kompilácie, na rozdiel od
TypeScriptu).
Mágia dvojitej šípky
Jedna z vecí, ktorá môže byť v JavaScripte mätúce, je
this. Tí, čo robia s jQuery, sa s týmto problémom často
stretávajú. Majme stránku, kde je tlačidlo s id 'klik':
$('#klik').on 'click', -> do -> alert @
JavaScript:
$('#klik').on('click', function() { return (function() { return alert(this); })(); });
Pri kliknutí sa zobrazí [object Window]. Keby sme IIFE nahradili iba
funkcií alert(this), zobrazí sa [object HTMLButtonElement]. Túto
situáciu, kedy nám 'toto' nahrádza 'tamto', keď to nechceme, rieši
CoffeeScript znakom =>
$('#klik').on 'click', -> do => alert @
JavaScript:
$('#klik').on('click', function() { return (function(_this) { return function() { return alert(_this); }; })(this)(); });
Okrem jQuery je táto šípka tiež veľmi užitočná práve pre triedy. Vytvoríme si triedu, v ktorej bude konštruktor, jedna "jednoduchá" metóda a jedna "dvojitá". Potom tieto metódy pošleme ako callback do funkcie f:
class Area constructor: -> @x = 51 jedna: -> alert @x dve: => alert @x a = new Area a.jedna() # 51 a.dve() # 51 f = (funkce) -> funkce() f(a.jedna) # undefined f(a.dve) # 51 f(-> a.jedna()) # 51
Vo výslednom JavaScriptu nás zaujímajú hlavne tieto dva riadky:
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; // v těle konstruktoru this.dve = __bind(this.dve, this);
Ktoré "prišpendlia" danú funkciu k objektu, kde je definovaná, a pomocou nej sa potom môžeme odkazovať na vlastnosti objektu aj v inom kontexte. Za krásny príklad vyššie ďakujem stránke StackOverflow.
A čo statika?
Áno, aj s tou môžete pracovať. Keďže trieda je tu vlastne objekt,
this označuje v definícii samotnú triedu (konštruktor).
Statickú vlastnosť / metódu vytvoríme jednoducho:
class Trida notStaticM: -> alert 'nejsem static' @staticA: 0 @staticM: -> ++@staticA alert Trida.staticA # 0 Trida.staticM() alert Trida.staticA # 1 Trida.notStaticM() # TypeError: undefined is not a function t = new Trida alert t.staticA # undefined t.staticM() # TypeError: undefined is not a function
V JS môžeme krásne vidieť, že statické metódy a vlastnosti sa
priraďujú priamo ku 'triede', teda o objekte v premennej Trida,
zatiaľ čo ne-statické sa priraďujú k prototypu:
// tělo definice Trida.prototype.notStaticM = function() { return alert('nejsem static'); }; Trida.staticA = 0; Trida.staticM = function() { return ++this.staticA; // ... };
Ak ste sa dostali až sem, gratulujem vám, ste u konca seriálu. Ja dúfam, že sa vám páčil a že pre vás bude od teraz CoffeeScript dôležitý nástroj pri realizovaní vašich nápadov na ovládnutie sveta. Ak nie, tak snáď máte o trošku lepší vhľad do JavaScriptu.
V nasledujúcom kvíze, Kvíz - CoffeeScript, si vyskúšame nadobudnuté skúsenosti z kurzu.

