70-480 – Tworzenie i wdrażanie obiektów i metod

70-480

Wdrażanie obiektów natywnych; tworzenie obiektów i właściwości niestandardowych dla obiektów natywnych przy użyciu prototypów i funkcji; dziedziczenie z obiektu; wdrażanie metod macierzystych i tworzenie metod niestandardowych.



To już ostatnia część sekcji Wdrażanie i edycja struktur i obiektów dokumentu, która stanowi około 24% całego materiału do opanowania. Przygotowania do egzaminu 70-480 można uznać za zaawansowane.

Zajmę się dzisiaj obiektami natywnymi w JavaScript, tworzeniem własnych obiektów oraz ich rozszerzaniem w ramach dziedziczenia. Pokażę także wzorzec tworzenia modułu, z którego często korzystam pisząc aplikacje w AngularJS.

Natywnie w JavaScript

JavaScript dostarcza nam szereg natywnych obiektów i metod, które możemy wykorzystywać w naszym kodzie beż żadnych dodatkowych bibliotek. Często możemy sobie nawet nie zdawać sprawy, że przypisując do zmiennej liczbę, tworzony jest nowy obiekt new Number(liczba), czy przypisując wartość tekstową, tworzony jest obiekt new String(tekst). Wszystkie obiekty tworzone są ponad to na podstawie głównego obiektu Object. Jest to sposób na dziedziczenie wspólnych cech obiektów znane z wielu języków programowania.

Na stronie w3schools.com znajdziemy opisy wszystkich podstawowych obiektów oraz ich metody:

Poza tym, w JavaScript dostępne są także wartości specjalne (null, undefined oraz operator typeof).

Różnica pomiędzy null, a undefined przedstawia się następująco:

typeof undefined		// undefined
typeof null				// object
null === undefined		// false
null == undefined		// true
var person = null;

– wartość null, typ object

var person = undefined;

– wartość undefined, typ undefined

Rozszerzanie natywnych obiektów w JavaScript

Większość typów w JavaScript posiada natywne, wbudowane metody oraz dodatkowe pola, które budują domyślne zachowania. Spójrzmy choćby na typ Function:

Object.getOwnPropertyNames(Function.prototype)
// ["length", "name", "arguments", "caller", "constructor", "bind", "toString", "call", "apply", "toMethod"]

Każda nowo tworzona funkcja posiada również te metody. Nie możemy ich edytować oraz zmieniać, ale możemy dodawać nowe metody oraz dodawać lub edytować istniejące pola do już istniejących typów. Służy do tego model prototype, w którym zawarte są wszystkie natywne oraz domyślne dla każdego tworzonego obiektu (new), metody oraz właściwości.

Stworzę w ten sposób nową (prostą i lekko naiwną) metodę dla typu Array, która usunie dany element z tablicy:

Array.prototype.remove = function(member) {
  var index = this.indexOf(member);
  if (index > -1) {
    this.splice(index, 1);
  }
  return this;
}

Metoda stara się znaleźć element w tablicy za pomocą funkcji indexOf, a następnie usuwa go za pomocą metody splice. W przeciwnym wypadku nie robi nic. Na koniec zwraca referencje do samej siebie, co pozwala wywoływać metody jedna po drugiej.

Jej użycie jest od tej chwili możliwe na zbiorze danych znajdujących się w tablicy:

['one', 'two', 'three'].remove('two'); // ["one", "three"]
['one', 'two', 'three'].remove('four'); // ["one", "two", "three"]

Dziedziczenie w JavaScript

Wiedząc już o własności prototype, możemy skupić się na jednej z najważniejszych cech programowania obiektowego, a mianowicie dziedziczeniu. Jest ono dostępne zarówno w JavaScript, jako w pełni obiektowym języku.

Poniżej przykład dziedziczenia opartego o prototypy. Tworząc obiekt, który dziedziczyć ma metody oraz właściwości swojego rodzica musimy przypisać do pola prototype klasy dziedziczącej referencję do obiektu rodzica.

function Parent() {}

Parent.prototype.setName = function (name) {
	this.name = 'Parent: ' + name;
};

Parent.prototype.getName = function () {
	console.log(this.name);
};
function Child() {}

Child.prototype = new Parent();

Child.prototype.setName = function (name) {
	this.name = 'Child: ' + name;
};
var parent = new Parent();
parent.setName('Mark');
parent.getName();

var child = new Child();
child.setName('Rob');
child.getName();

Na konsoli przeglądarki otrzymamy w ten sposób:

// Parent: Mark
// Child: Rob

Metoda setName w obiekcie Child została przysłonięta przez własną implementację. Metoda getName została natomiast w pełni odziedziczona z obiektu Parent. Jest to na tyle proste, że nie ma chyba sensu opisywać tego dalej.

Wzorzec modułu

Jak już wspomniałem we wstępie wpisu, pisząc aplikacje w AngularJS często korzystam ze wzorca modułu. Można go spotkać choćby w serwisach, o których pisałem we wpisie: Factory vs Service vs Provider.

Idea znów jest bardzo prosta. Chcemy stworzyć moduł, który posiadać będzie swoją implementację wewnętrzną (prywatne metody oraz zmienne) oraz metody publiczne, wystawione na świat. Spójrzmy od razu na przykład:

var UserModule = function () {
	
  var _username = '',
    _setUsername = function (username) {
      _username = username;
    },
    _sayHello = function () {
      return 'Hello, ' + _username;
    };
    
  return {
    setUsername: _setUsername,
    sayHello: _sayHello
  };
	
};

var UserModule = new UserModule();
UserModule.setUsername('mrzepinski');
console.log(UserModule.sayHello());

Dzięki takiej definicji możemy enkapsulować swój kod i tworzyć prywatne metody oraz zmienne. Poprzez użycie obiektu zwracanego za pomocą słowa return, wystawiamy tylko te metody, które zostały przez nas odpowiednio przygotowane. W tym wypadku zmienna _username oraz metody _setUsername i _sayHello dostępne są tylko wewnątrz implementacji modułu.

  • Michal

    “w JavaScript, jako w pełni obiektowym języku.” ? :) really ?

    • Może jest to małe nadużycie z mojej strony, ale jednak JavaScript posiada bardzo dużo cech OOP. Nie mówiąc już o ES6.