Objektový JavaScript pohledem Javisty

Vychází především z popisu na stránkách Mozzily. V JS světě existuje více přístupů k řešení dědičnosti(!), ale od toho v Javě se vždy něčím liší a není vhodné snažit se s JS pracovat s objekty vždy stejně jako v Javě.

Objekty v JavaScriptu

V JS je objekt vše, co není primitivní typ.

Objekt je v JS asociativní pole a tak s ním lze i zacházet.

Objektem je i funkce. JS hodně pracuje s tím, že funkce je objektem. Díky tomu může být předána jako parametr a nebo být vrácena jinou funkcí. To má vliv také na OOP v JS. Mimo jiné metoda objektu je zase jen funkce přiřazená do nějaké proměnné objektu.

Objekty v JS jsou založeny na prototypu (viz později), který nerozlišuje objekt od třídy v takové míře jako Java. Definice třídy je součástí jejího konstruktoru.

Inicializátor objektu a objektový literál

Objekt lze v JS definovat současně s jeho instanciováním (vytvořením instance právě definované třídy). Jde o tzv. objektový inicializátor (object initializer). V takové případě stačí deklarovat proměnnou, které rovnou přiřadíme “tělo třídy” ve složených závorkách, např.:

var person = {};

Vlastnosti (proměnné a metody) objektu se v takovém případě definují pomocí tzv. objektového literálu, což je dvojice název-hodnota od sebe oddělená dvojtečkou. Hodnotou vlastnosti za dvojtečkou může být primitivní typ nebo objekt. Pokud je tímto objektem funkce (její definice uvozená klíčovým slovem function), pak se jí stejně jako v Javě říká metoda.

Jednotlivé vlastnosti se oddělují čárkou.

var person = {
  name: ['Jan', 'Novak'],
  age: 32,
  greeting: function() {
    alert('Hi ' + this.name[0]);
  },
  greetingFormal: function() {
    alert('Dear ' + this.name[1]);
  }
};

Objekty lze do sebe zanořovat. Vlastnost tak může být opět objektem jako je tomu u name v příkladu:

var person = {
  name: {
    first: 'Jan',
    family: 'Novak'
  }
};

K vlastnostem přistupujeme stejně jako v Javě přes tečkovou notaci, např.:

person.name
person.name[0] //původní příklad s polem
person.name.first //příklad se zanořením
person.greeting()

Objekt v JS je v podstatě asociativní pole, čemuž odpovídá druhý přístup k vlastnostem, např.:

person['name']['first']

Přiřazení hodnoty se provádí opět oběma způsoby (přes tečkovou notaci i asociativní pole).

person.name.first = 'Kosik'; //změní hodnotu vlastnosti
person['name']['first'] = 'Kosik'; //vytvoří novou vlastnost a přiřadí jí hodnotu

Přiřazením lze dokonce vytvářet i nové vlastnosti (vč. metod, ty lze vytvářet i přes tečkovou notaci). Pokud dosud neexistuje vlastnost s daným názvem, tak se vytvoří.

person['height'] = '180'; //vytvoří novou vlastnost a přiřadí jí hodnotu

Název takto vytvářené vlastnosti může být libovolný řetězec, který lze předat proměnnou. Tím lze oproti silně typované Javě vlastnosti vytvářet dynamicky za běhu.

person[label] = inputVal; //vytvoří novou vlastnost s názvem v proměnné label a hodnotou inputVal

Pomocí for...in lze iterovat přes vlastnosti objektu (resp. asociativní pole, kterým je reprezentován).

Nepoděděné vlastnosti lze i odebírat pomocí operátoru delete.

Dynamické přidávání vlastností objektu nám umožňuje vytvořit obecný objekt a postupně mu přidat požadované vlastnosti aniž bychom je dříve specifikovali.

var person1 = new Object();
person1.name = 'Jan';
person1['age'] = 38;
person1.greeting = function() {
   alert('Hi ' + this.name);
};

Alternativně lze vlastnosti definovat jako literály uvnitř parametru konstruktoru Object.

var person1 = new Object({
  name : 'Jan',
  age : 38,
 greeting : function() {
    alert('Hi ' + this.name);
  }
});

Pokud vlastnosti není přiřazena hodnota, pak je undefined a nikoliv null.

person1.color; // undefined

Pokud chceme, aby byla hodnota null, musíme ji explicitně přiřadit.

Vytvoření jednoho objektu z existujícího lze pomocí Object.create. Metodě create předáme objekt a ta vytvoří jeho kopii se stejnými vlastnostmi (proměnnými a metodami). Tento způsob ale není podporován v IE8.

var person2 = Object.create(person1);

Konstruktor (konstrukční funkce)

Výše uvedené způsoby slouží pro rychlé přidání objektů (často můžeme chtít jen pár instancí) a je dobré o nich vědět, když na ně člověk narazí. Princip objektových literálů se navíc může hodit pro zanoření jednoduchých tříd (viz vlastnost name).

Kvůli znovupoužitelnosti a vytváření libovolného počtu instancí nejspíš sáhneme po konstruktoru tak, abychom mohli hodnoty proměnných předat vytvářenému objektu jako v Javě.

Konstruktor v JS definujeme jako běžnou funkci (od těch se liší jen konvencí, kterou je název začínající velkým písmenem stejně jako je tomu Javě). Uvnitř definujeme jeho vlastnosti (vč. metod), ale přes klíčové slovo this místo objektového literálu (dvojice název-hodnota oddělené dvojtečkou). Objektový literál ale může být zanořený uvnitř konstruktoru (jako je tomu u name v příkladu). Př.:

function Person(first, last, age) {
  this.name = {
     first : first,
     last : last
  };
  this.age = age;
  this.greeting = function() {
    alert('Hi ' + this.name.first);
  };
}

Výhodou JS je možnost předat konstruktoru méně parametrů, než kolik jich definuje. JS tak oproti Javě sám implicitně zajistí “přětížení” konstruktoru.

function Person(name, age) {
  this.name || '';
  this.age = age || 0;
}

...je v JS obdobou následujícího v Javě:

public class Person {
   public String name;
   public int age;
   public Person () {
      this("", 0);
   }
   public Person (String name) {
      this(name, 0);
   }
   public Person (String name, int age) {
      this.name = name;
      this.age = age;
   }

Instancování

Stejně jako v Javě volání konstruktoru předchází klíčové slovo new.

Vytvoří se tím instance objektu podle prototype (s vlastnostmi danými definicí třídy, více viz níže), provede se konstruktor a je vrácen tento objekt (čímž se liší od běžného volání funkce bez new).

Klíčové slovo this

Oproti Javě má klíčové slovo this trochu širší použití a chová se jinak než v thisčistě objektových jazycích. Uvnitř třídy se ale chová stejně a odkazuje na aktuální instanci objektu. Mimo třídu obvykle odkazuje na globální objekt window.

Getter, setter

ECMAScript 5 (2009) umožňuje definovat getter a setter pomocí klíčových slov get a set. Jejich použití může být matoucí, protože účel není ekvivalentní tomu v Javě. V JS jsou určeny pro tzv. počítané vlastnosti, kdy getter má vrátit modifikovanou/vypočtenou vlastnost a setter má nahradit vlastnost za vypočtenou hodnotu.

Definovat lze buď při vytváření nového objektu pomocí inicializátoru objektu:

var person = {
  firstName : 'Jan',
  lastName : 'Novak',
  get fullName() {
    return this.firstName + " " + this.lastName;
  },
  set fullName(fullName) {
    var names = fullName.split(' ');
    this.firstName = names[0];
       this.lastName = names[1];
  }
};

...nebo přidáním vlastnosti existující třídě pomocí metody Object.defineProperty. Nelze je definovat v konstruktoru.

function Person() {
  this.firstName = 'Jan',
  this.lastName = 'Novak'
}
 
Object.defineProperty(Person.prototype, 'fullName', {
  get: function() { 
    return this.firstName + " " + this.lastName; 
  },
  set: function(fullName) {
    var names = fullName.split(' ');
    this.firstName = names[0];
       this.lastName = names[1];
  }
});

Rozdíl getteru/setteru od běžné funkce je především ten, že se volá stejně jako proměnná (bez závorek za názvem). Př.:

alert(person.fullName) // Jan Novak
person.fullName = 'Karel Marx';

(Ne)zapouzdření

Ačkoliv lze pomocí get/set již v ECMAScript 5 vytvořit obdobný getter/setter jako v Javě, nemá použití v prototypové dědičnosti příliš smysl a někdy bývá považováno za chybný postup:

var _occupation = null;
Object.defineProperty(Person.prototype, 'occupation', {
  get: function() { return this._occupation; },
  set: function(o) { this._occupation = o; }
});

Pokud ve třídě použijeme var, tak proměnnou “zapouzdříme”. Tento postup ale není vhodný k dosažení omezené viditelnosti vlastností třídy jako se to dělá v Javě. Návrh jazyka se zapouzdřením nepočítá. Princip omezení přístupu k proměnným třídy pouze přes getter/setter nelze v JS ani úplně vynutit, a tak nemá smysl snažit se za každou cenu dosáhnout omezení viditelnosti pomocí klíčového slova var (ačkoliv se to v některých návodech objevuje). V JS to způsobuje problémy s dosažením dědičnosti. Obvykle slovem var označujeme vnitřní proměnné třídy, které nejsou zamýšleny jako její vlastnosti, například různé pomocné proměnné.

Smysluplné gettery/settery jako je známe z Javy mají smysl až v kombinaci s class od ECMAScript 2015, která ale má podporu jen v nových prohlížečích.

Porovnání objektů

V JS jsou objekty stejně jako v Javě referenčního typu, a tak jsou dva objekty rovny pouze pokud odkazují stejnou referenci.

var p1 = new Person('A','B', 0);
prevar p2 = new Person('A','B', 0);
p1 == p2; //false
p1 === p2; //false

Na příspěvek navazuje další podrobněji se zabývající dědičností v JavaScriptu.