Ponieważ zadałeś podobne pytanie , zróbmy to krok po kroku. Jest nieco dłuższy, ale może zaoszczędzić znacznie więcej czasu, niż poświęciłem na napisanie tego:
Właściwość jest funkcją OOP zaprojektowaną do czystego oddzielania kodu klienta. Na przykład w pewnym sklepie internetowym możesz mieć takie obiekty:
function Product(name,price) {
this.name = name;
this.price = price;
this.discount = 0;
}
var sneakers = new Product("Sneakers",20); // {name:"Sneakers",price:20,discount:0}
var tshirt = new Product("T-shirt",10); // {name:"T-shirt",price:10,discount:0}
Następnie w kodzie klienta (e-sklepie) możesz dodać rabaty do swoich produktów:
function badProduct(obj) { obj.discount+= 20; ... }
function generalDiscount(obj) { obj.discount+= 10; ... }
function distributorDiscount(obj) { obj.discount+= 15; ... }
Później właściciel e-sklepu może zdać sobie sprawę, że rabat nie może przekroczyć 80%. Teraz musisz znaleźć KAŻDY przypadek modyfikacji rabatu w kodzie klienta i dodać wiersz
if(obj.discount>80) obj.discount = 80;
Następnie właściciel e-sklepu może zmienić strategię, na przykład „jeśli klient jest sprzedawcą, maksymalna zniżka może wynosić 90%” . I musisz ponownie wprowadzić zmiany w wielu miejscach, a także pamiętać o zmianie tych linii za każdym razem, gdy zmienia się strategia. To zły projekt. Właśnie dlatego kapsułkowanie jest podstawową zasadą OOP. Jeśli konstruktor był taki:
function Product(name,price) {
var _name=name, _price=price, _discount=0;
this.getName = function() { return _name; }
this.setName = function(value) { _name = value; }
this.getPrice = function() { return _price; }
this.setPrice = function(value) { _price = value; }
this.getDiscount = function() { return _discount; }
this.setDiscount = function(value) { _discount = value; }
}
Następnie możesz po prostu zmienić metody getDiscount
( akcesor ) i setDiscount
( mutator ). Problem polega na tym, że większość członków zachowuje się jak wspólne zmienne, tylko zniżka wymaga tutaj szczególnej uwagi. Ale dobry projekt wymaga enkapsulacji każdego elementu danych, aby kod był rozszerzalny. Musisz więc dodać dużo kodu, który nic nie robi. Jest to również zły projekt, antipattern z płytą grzejną . Czasami nie można po prostu zmienić pól na metody później (kod eshopu może się rozrosnąć lub jakiś kod innej firmy może zależeć od starej wersji), więc podstawa jest tutaj mniejsza zła. Ale wciąż jest zły. Właśnie dlatego właściwości zostały wprowadzone w wielu językach. Możesz zachować oryginalny kod, po prostu przekształć członka rabatowego we właściwość za pomocąget
i set
bloki:
function Product(name,price) {
this.name = name;
this.price = price;
//this.discount = 0; // <- remove this line and refactor with the code below
var _discount; // private member
Object.defineProperty(this,"discount",{
get: function() { return _discount; },
set: function(value) { _discount = value; if(_discount>80) _discount = 80; }
});
}
// the client code
var sneakers = new Product("Sneakers",20);
sneakers.discount = 50; // 50, setter is called
sneakers.discount+= 20; // 70, setter is called
sneakers.discount+= 20; // 80, not 90!
alert(sneakers.discount); // getter is called
Uwaga na przedostatni wiersz: odpowiedzialność za prawidłową wartość rabatu została przeniesiona z kodu klienta (definicja sklepu internetowego) do definicji produktu. Produkt jest odpowiedzialny za utrzymanie spójności swoich członków danych. Dobry projekt jest (z grubsza powiedziane), jeśli kod działa tak samo, jak nasze myśli.
Tyle o właściwościach. Ale javascript różni się od czysto obiektowych języków, takich jak C #, i koduje funkcje inaczej:
W języku C # przekształcanie pól we właściwości jest przełomową zmianą , więc pola publiczne powinny być zakodowane jako właściwości automatycznie implementowane, jeśli kod może być używany w osobno skompilowanym kliencie.
W Javascript standardowe właściwości (element danych z geterem i seterem opisanym powyżej) są zdefiniowane przez deskryptor akcesora (w linku, który masz w swoim pytaniu). Wyłącznie możesz użyć deskryptora danych (więc nie możesz użyć np. Wartości i ustawić tej samej właściwości):
- accessor descriptor = get + set (patrz przykład powyżej)
- get musi być funkcją; jego wartość zwracana jest używana do odczytu właściwości; jeśli nie zostanie określony, wartość domyślna jest niezdefiniowana , która zachowuje się jak funkcja, która zwraca niezdefiniowaną wartość
- set musi być funkcją; jego parametr jest wypełniony RHS przy przypisywaniu wartości do właściwości; jeśli nie zostanie określony, wartość domyślna jest niezdefiniowana , co działa jak pusta funkcja
- deskryptor danych = wartość + zapis (patrz przykład poniżej)
- wartość domyślna niezdefiniowana ; jeśliprawda jest zapisywalna , konfigurowalna i wyliczalna (patrz poniżej), to właściwość zachowuje się jak zwykłe pole danych
- zapisywalny - domyślnie false ; jeśli nie jest prawdziwe , właściwość jest tylko do odczytu; próba zapisu jest ignorowana bez błędu *!
Oba deskryptory mogą mieć następujących członków:
- konfigurowalny - domyślnie false ; jeśli nie jest prawdą, właściwości nie można usunąć; próba usunięcia jest ignorowana bez błędu *!
- wyliczalny - domyślnie false ; jeśli to prawda, będzie iterowane
for(var i in theObject)
; jeśli false, nie będzie iterowany, ale nadal będzie dostępny jako publiczny
* chyba że w trybie ścisłym - w takim przypadku JS zatrzymuje wykonywanie z TypeError, chyba że zostanie złapany w bloku try-catch
Aby odczytać te ustawienia, użyj Object.getOwnPropertyDescriptor()
.
Dowiedz się na przykładzie:
var o = {};
Object.defineProperty(o,"test",{
value: "a",
configurable: true
});
console.log(Object.getOwnPropertyDescriptor(o,"test")); // check the settings
for(var i in o) console.log(o[i]); // nothing, o.test is not enumerable
console.log(o.test); // "a"
o.test = "b"; // o.test is still "a", (is not writable, no error)
delete(o.test); // bye bye, o.test (was configurable)
o.test = "b"; // o.test is "b"
for(var i in o) console.log(o[i]); // "b", default fields are enumerable
Jeśli nie chcesz zezwalać kodowi klienta na takie kody, możesz ograniczyć obiekt przez trzy poziomy ograniczenia:
- Object.preventExtensions (yourObject) zapobiega dodawaniu nowych właściwości do twojego obiektu . Służy
Object.isExtensible(<yourObject>)
do sprawdzania, czy na obiekcie użyto metody. Zapobieganie jest płytkie (czytaj poniżej).
- Object.seal (yourObject) to samo co powyżej i właściwości nie można usunąć (skutecznie ustawia
configurable: false
wszystkie właściwości). SłużyObject.isSealed(<yourObject>)
do wykrywania tej funkcji na obiekcie. Pieczęć jest płytka (czytaj poniżej).
- Object.freeze (yourObject) to samo co powyżej i właściwości nie można zmieniać (efektywnie ustawia się
writable: false
na wszystkie właściwości z deskryptorem danych). Nie ma to wpływu na zapisywalną właściwość Settera (ponieważ jej nie ma). Zamrożenie jest płytkie : oznacza to, że jeśli właściwość to Object, wówczas jej właściwości NIE SĄ zamrożone (jeśli chcesz, powinieneś wykonać coś w rodzaju „głębokiego zamrożenia”, podobnie jak głębokie kopiowanie - klonowanie ). Użyj,Object.isFrozen(<yourObject>)
aby go wykryć.
Nie musisz się tym przejmować, jeśli piszesz tylko kilka linijek zabawy. Ale jeśli chcesz zakodować grę (jak wspomniałeś w łączonym pytaniu), powinieneś naprawdę dbać o dobry projekt. Spróbuj wyszukać w Google coś na temat antypotów i zapachu kodu . Pomoże Ci to uniknąć sytuacji takich jak „Och, muszę ponownie całkowicie przepisać mój kod!” , może zaoszczędzić miesięcy rozpaczy, jeśli chcesz dużo kodować. Powodzenia.