Przejdź do treści

Dziedziczenie obiektów w JavaScript (bez użycia klas)

Od 2015 roku dzięki specyfikacji ES6 a.k.a. ES2015 mamy możliwość stworzenia klasy jako reprezentacji opisu obiektu, na podstawie której zostanie on stworzony.

Co jeśli architektura Twojej aplikacji nie opiera się na klasach? A może po prostu nie jesteś zwolennikiem Object-Oriented Programming (OOP)?

Taka sytuacja jest dosyć częsta, kiedy zaczynasz poznawać świat programowania w JavaScript i klasy nie są jeszcze Ci znane.

Z pomocą przychodzi wzorzec delegacji i styl Objects Linked to Other Objects (OLOO), który swoją definicję posiada w książce “You Don’t Know JS” autorstwa Kyle Simsona.

Dziedziczenie

Przykładowy model architektury w ujęciu klasycznym, czyli istnieją obiekty główne (rodzice) oraz obiekty pochodne (dzieci).

Schemat obiektów prostej architektury aplikacji

Dziedziczenie obiektów? Nie!

Model architektury opartej na OLOO nie wprowadza hierarchii. On po prostu informuje, że jeden obiekty będą korzystały z innych.

Schemat obiektów prostej architektury aplikacji

Tego procesu nie można nazwać dziedziczeniem, tylko delegacją.

W specyfikacji ES2015 została zdefiniowana funkcja statyczna Object.setPrototypeOf, która łączy ze sobą obiekty za pomocą wiązania __proto__. Wiązanie te występuje TYLKO w obiektach, w funkcjach “od zawsze” istnieje inne wiązanie, tj: “prototype”.

const point = {
    x: null,
    y: null,
    draw() {
        // ...
    }
};

const point1 = {
    x: 0,
    y: 0
};
console.log(typeof point1.draw); // undefined

Object.setPrototypeOf(point1, point);

console.log(point1.hasOwnProperty('draw')); // false
console.log('draw' in point1); // true
console.log(typeof point1.draw); // "function"

const point2 = {
    x: 50,
    y: 50
};
Object.setPrototypeOf(point2, point);

Weryfikacja

Aby móc zweryfikować aktualny stan proponuję uruchomić:

// Check: Different points
console.log(point1 !== point2) // true

// Same definition of "draw" method
console.log(point.draw === point1.draw) // true
console.log(point.draw === point2.draw) // true
console.log(point1.draw === point2.draw) // true

Wsparcie systemowe

Ale gdzie można używać takiej składni zapytasz? Otóż dzięki najpopularniejszemu zestawieniu wsparcia feature-ów JavaScript-owych, realizowanemu przez Kangax-a, możemy się dowiedzieć, że:

Jest wsparcie

  • Google Chrome 57+ (Opera 44+)
  • Firefox 52+
  • Safari:
    • desktop: 10+
    • mobile: 9+
  • Microsoft
    • IE 11
    • Edge 14+
  • Node.js 4+

Brak wsparcia

Niestety, ale żaden transpiler nie pomoże w zapewnieniu wsparcia wykorzystania funkcji Object.setPrototypeOf:

  • Babel.js
  • Traceur
  • Closure
  • TypeScript

Link do pełnego zestawienia.

Polyfill

Link do definicji polyfilla znajduje się na MDN jednak, abyś mógł szybko zobaczyć jak on wygląda umieszczę go tutaj:

// Only works in Chrome and FireFox, does not work in IE:
Object.setPrototypeOf = Object.setPrototypeOf || function(obj, proto) {
  obj.__proto__ = proto;
  return obj;
}