Istnieją dwa rodzaje właściwości obiektów.

Pierwszym rodzajem są właściwości danych. Wiemy już, jak z nimi pracować. Wszystkie właściwości, których używaliśmy do tej pory, były właściwościami danych.

Drugi rodzaj właściwości jest czymś nowym. Są to właściwości accessor. Są to w zasadzie funkcje, które wykonują się przy pobieraniu i ustawianiu wartości, ale dla zewnętrznego kodu wyglądają jak zwykłe właściwości.

Getters and setters

Właściwości akcesora są reprezentowane przez metody „getter” i „setter”. W literale obiektowym są one oznaczane przez get i set:

let obj = { get propName() { // getter, the code executed on getting obj.propName }, set propName(value) { // setter, the code executed on setting obj.propName = value }};

Getter działa, gdy obj.propName jest odczytywany, setter – gdy jest przypisywany.

Na przykład mamy obiekt user z name i surname:

let user = { name: "John", surname: "Smith"};

Teraz chcemy dodać właściwość fullName, która powinna być "John Smith". Oczywiście nie chcemy kopiuj-wklej istniejącej informacji, więc możemy ją zaimplementować jako accessor:

let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }};alert(user.fullName); // John Smith

Z zewnątrz właściwość accessor wygląda jak zwykła. Na tym właśnie polega idea właściwości accessor. Nie wywołujemy user.fullName jako funkcji, tylko czytamy ją normalnie: getter działa za kulisami.

Jak na razie, fullName ma tylko getter. Jeśli spróbujemy przypisać user.fullName=, pojawi się błąd:

let user = { get fullName() { return `...`; }};user.fullName = "Test"; // Error (property has only a getter)

Poprawmy to, dodając setter dla user.fullName:

let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }, set fullName(value) { = value.split(" "); }};// set fullName is executed with the given value.user.fullName = "Alice Cooper";alert(user.name); // Alicealert(user.surname); // Cooper

W rezultacie mamy „wirtualną” właściwość fullName. Jest ona odczytywalna i zapisywalna.

Deskryptory dla właściwości accessor

Deskryptory dla właściwości accessor różnią się od deskryptorów dla właściwości data.

Dla właściwości accessor nie ma value lub writable, ale zamiast tego są funkcje get i set.

To znaczy, deskryptor dostępu może mieć:

  • get – funkcję bez argumentów, która działa, gdy właściwość jest odczytywana,
  • set – funkcję z jednym argumentem, która jest wywoływana, gdy właściwość jest ustawiana,
  • enumerable – tak samo jak dla właściwości danych,
  • configurable – tak samo jak dla właściwości danych.

Na przykład, aby utworzyć accessor fullName z defineProperty, możemy przekazać deskryptor z get i set:

let user = { name: "John", surname: "Smith"};Object.defineProperty(user, 'fullName', { get() { return `${this.name} ${this.surname}`; }, set(value) { = value.split(" "); }});alert(user.fullName); // John Smithfor(let key in user) alert(key); // name, surname

Proszę zauważyć, że właściwość może być albo accessorem (ma get/set metod), albo właściwością danych (ma value), a nie obydwoma.

Jeśli spróbujemy dostarczyć zarówno get, jak i value w tym samym deskryptorze, wystąpi błąd:

// Error: Invalid property descriptor.Object.defineProperty({}, 'prop', { get() { return 1 }, value: 2});

Smarter getters/setters

Getters/setters mogą być używane jako wrappery nad „prawdziwymi” wartościami właściwości, aby uzyskać większą kontrolę nad operacjami z nimi.

Na przykład, jeśli chcemy zabronić zbyt krótkich nazw dla user, możemy mieć setter name i przechowywać wartość w oddzielnej właściwości _name:

let user = { get name() { return this._name; }, set name(value) { if (value.length < 4) { alert("Name is too short, need at least 4 characters"); return; } this._name = value; }};user.name = "Pete";alert(user.name); // Peteuser.name = ""; // Name is too short...

Więc, nazwa jest przechowywana we właściwości _name, a dostęp do niej odbywa się poprzez getter i setter.

Technicznie, kod zewnętrzny jest w stanie uzyskać bezpośredni dostęp do nazwy za pomocą user._name. Ale istnieje powszechnie znana konwencja, że właściwości zaczynające się od podkreślnika "_" są wewnętrzne i nie powinny być dotykane spoza obiektu.

Użycie dla kompatybilności

Jednym z wielkich zastosowań accessorów jest to, że pozwalają one w każdej chwili przejąć kontrolę nad „zwykłą” właściwością danych poprzez zastąpienie jej getterem i setterem i podrasowanie jej zachowania.

Wyobraźmy sobie, że zaczęliśmy implementować obiekty użytkownika używając właściwości danych name i age:

function User(name, age) { this.name = name; this.age = age;}let john = new User("John", 25);alert( john.age ); // 25

…Ale prędzej czy później wszystko może się zmienić. Zamiast age możemy zdecydować się na przechowywanie birthday, ponieważ jest to bardziej precyzyjne i wygodne:

function User(name, birthday) { this.name = name; this.birthday = birthday;}let john = new User("John", new Date(1992, 6, 1));

A teraz co zrobić ze starym kodem, który nadal używa właściwości age?

Możemy spróbować znaleźć wszystkie takie miejsca i je naprawić, ale to wymaga czasu i może być trudne do wykonania, jeśli ten kod jest używany przez wiele innych osób. A poza tym age jest miłą rzeczą, którą warto mieć w user, prawda?

Zachowajmy ją.

Dodanie gettera dla age rozwiązuje problem:

function User(name, birthday) { this.name = name; this.birthday = birthday; // age is calculated from the current date and birthday Object.defineProperty(this, "age", { get() { let todayYear = new Date().getFullYear(); return todayYear - this.birthday.getFullYear(); } });}let john = new User("John", new Date(1992, 6, 1));alert( john.birthday ); // birthday is availablealert( john.age ); // ...as well as the age

Teraz stary kod też działa, a my mamy fajną dodatkową właściwość.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.