IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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:

  1. 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)
  2. Prvá časť tela funkcie __extends nakopíruje vlastnosti predka do potomka (aj ich hodnoty!)
  3. 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;
  1. 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 sa super muselo riešiť volaním predka jeho mene, čo môže spôsobiť problémy.
  2. 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ódou call(), 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.


 

Predchádzajúci článok
Funkcie a výnimky v CoffeeScript
Všetky články v sekcii
CoffeeScript
Preskočiť článok
(neodporúčame)
Kvíz - CoffeeScript
Článok pre vás napísal Yahkem
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
...
Aktivity