ES6#1 Czy JavaScript jest językiem zorientowanym obiektowo?

AngularJS, w którym piszę swój projekt, jest frameworkiem który promuje obiektowe podejście do programowania, jednak na początek warto byłoby zadać sobie pytanie, czy JavaScript jest językiem zorientowanym obiektowo? Postaram się wyjaśnić czy jest to prawdą, oraz wytłumaczyć jak JavaScript różni się od innych języków. Zahaczymy także o najnowszy standard tego języka i jego podejście do programowania obiektowego. Zaczynajmy!

# Czy JavaScript jest językiem zorientowanym obiektowo?

JavaScript jako jeden z niewielu języków programowania jest zorientowany obiektowo. Zaraz, zaraz, zapytacie - przecież inne języki też mają obiektowość! Jednak takie popularne języki jak C++ czy Python powinny nazwane być precyzyjniej "języki klasowo zorientowane". JavaScript różni się od nich tym, że w ogóle nie posiada klas (nie jest to do końca prawdą, ale wytłumaczę to w dalszej części tego posta). Czy taka różnica jest zaletą czy wadą? Wielu programistów uważa że dziedziczenie metod i zmiennych z klas to złe podejście, także możemy potraktować to jako zaletę. Klasy jednak, upraszczają wiele rzeczy o których musielibyśmy pamiętać w Javascripcie, dlatego sporo osób dąży do stworzenia klas w tym języku.

Wiele osób twierdzi, że w języku JavaScript wszystko jest obiektem, jednak nie jest to do końca prawdą. Argumentują to faktem, że np. string posiada swoje metody, takie jak length, jednak w rzeczywistości wygląda to inaczej. Sam string jest typem prymitywnym, jednak możemy stworzyć z niego obiekt:

 
var testString = "who am I";
typeof testString; //"string"
var testObject = new String("who am I");
typeof testObject; // "object"

W praktyce, jeśli zastosujemy na stringu funkcję taką jak length czy charAt, to język niejawnie tworzy z niego obiekt, a potem wywołuje daną funkcję.

# Jak wyglądają obiekty w JavaScript i czym są klasy?

Na początek stwórzmy obiekt z trzema własnościami:

 
var myObject = {
	firstVariable: 1,
	secondVariabe: 2,
	foo: function(){
		console.log("foo");
	}	
};

Możemy teraz odwoływać się do poszczególnych własności obiektu, niezależnie czy są funkcjami czy zmiennymi, za pomocą operatora . lub [ ]:

 
myObject.firstVariable; //1
myObject.secondVariable; //2
myObject["firstVariable"]; //1
myObject.foo; //"foo"

Klasa sama w sobie nie jest obiektem, tylko schematem budowy obiektów. Żeby otrzymać obiekt, na którym będzie można przykładowo wykonać jakąś metodę, musimy najpierw stworzyć obiekt, nazywany instancją tej klasy. Czym różni się dziedziczenie z klasy od dziedziczenia z obiektu? Jeśli tworzmy nowy obiekt z klasy, to wszystkie metody będą kopiowane do tego obiektu. W JavaScript dziedziczenie jest utrudnione, ponieważ nowy obiekt zamiast skopiować metodę obiektu nadrzędnego, tylko się do niej odwołuje.

# Co to jest prototyp i jak wygląda dziedziczenie?

Każdy obiekt w JavaScript ma wbudowaną funkcję, która nazywa się prototype i łączy go z innym obiektem. Obiekt, który nie dziedziczy nic z innego obiektu, ma przypisany prototyp do object.prototype. W momencie, w którym wywołujemy własność obiektu i program nie znajdzie jej w danym obiekcie, wtedy przesuwa się po nadrzędnych obiektach tak długo dopóki jej nie znajdzie, lub nie dotrze do końca łańcucha prototypów. W praktyce możemy przez to wywołać nieistniejącą w interesującym nas obiekcie metodę, a otrzymać odpowiedź od obiektu położonego wyżej w łańcuchu prototypów.

Funkcje prototypowe są dostępne dla każdej instancji. Nową instancję obiektu możemy stworzyć używając słowa kluczowego new:

 
function Droid(type) {
	this.type = type;
}

Droid.prototype.speak = function() {
	console.log("I am " + this.type );
};

var c3po = new Droid('protocol droid');

c3po.speak(); // I am protocol Droid

Wybacz jeśli nie jesteś fanem Star Wars, ale mam nadzieję że takie przykłady bardziej zapadną w pamięć :)

Nazywanie obiektów z dużej litery, to praktyka z innych języków programowania, jednak nie ma to wpływu na wykonanie kodu, chociaż możliwe jest, że niektóre lintery sprawdzające jakość kodu mogą tego wymagać.

Możemy teraz stworzyć kolejny obiekt, który będzie dziedziczył z funkcji Droid i tworzył także swoje instancje:

 
function Mech(type) {
	Droid.call(this, type);
}

Mech.prototype = new Droid();

var r2d2 = new Mech('astromech');
r2dr.speak(); // I am astromech

Prototyp funkcji Mech przypisaliśmy do obiektu Droid, dzięki czemu Mech dziedziczy wszystkie własności obiektu Droid. Użyliśmy funkcji call, która wywołuje nadrzędny obiekt z wartością this, wskazująca na obecny obiekt, dzięki temu ustawiając wartość this.type zapisuje się ona w obecnym obiekcie, zamiast nadrzędnym.

# Klasy i dziedziczenie w ES6

Jeśli chcesz pisać w ES6, musisz zainstalować kompilator babel lub przetestować kod online np. na stronie http://jsbin.com

Skoro w standardzie ES6 języka JavaScript pojawiło się słowo kluczowe class, czy oznacza to, że wprowadzono normalne klasy? Tak naprawdę zmieniła się tylko składnia kodu na krótszą i czytelniejszą, jednak za kulisami wszystko działa tak samo jak w przypadku funkcji z prototypem.

Spróbujmy teraz stworzyć klasę w ES6 i stworzyć jej instancję:

 
class Ship {
  constructor(options) {
    this.speed = options.speed;
    this.hasHyperdrive = options.hasHyperdrive;
  }

  getSpeed() {
    return this.speed;
  }
}

var TieFighter = new Ship({
  speed: 1200,
  hasHyperdrive: false
});

console.log(TieFighter.getSpeed()); //1050

W tym przypadku, nie widzimy już słowa kluczowego function w metodach. Wystarczy tylko nazwa, argumenty w okrągłym nawiasie i para nawiasów klamrowych. Nasz obiekt zwraca funkcją getSpeed jedną z wartości. Nowe instancje tworzymy za pomocą słowa kluczowego new, a funkcja constructor wywoływana jest automatycznie przy tworzeniu nowej instancji klasy.

Stwórzmy teraz kolejną klasę StarShip, która będzie dziedziczyła z klasy Ship:

 
class StarShip extends Ship {
	getSpeed() {
		var speed = super.getSpeed();
		console.log(speed, ", but Hyperdrive allows entering hyperspace");
	}
}

var FalconMillenium = new StarShip({
  speed: 1050,
  hasHyperdrive: true
});

FalconMillenium.getSpeed(); //"1050, but Hyperdrive allows entering hyperspace"

extends oznacza, że klasa StarShip dziedziczy wszystko z co klasa Ship posiada. Jednak w naszym kodzie skorzystaliśmy z polimorfizmu, który w ES6 także ma uproszczoną składnię.

# Na czym polega polimorfizm?

Polimorfizm umożliwia różne zachowanie tych samych metod. Jeśli w obiekcie nadpiszemy funkcję o tej samej nazwie, to pojawi się w łańcuchu prototypów jako pierwsza i właśnie ona się wykona.

W powyższym kodzie użyliśmy słowa kluczowego super, który pozwala na wywołanie funkcji klasy, z której dany obiekt dziedziczy. Dzięki temu możemy przypisać wartość do zmiennej i wyświetlić ją w inny sposób.

Published: April 02 2017

blog comments powered by Disqus