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
__extends
nakopíruje vlastnosti predka do potomka (aj ich hodnoty!) - Zabezpečia, že objekt
child.prototype
má 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ísuper
funkcie v CoffeeScriptu. Bez tohto riešenia by sasuper
muselo 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.