jak korzystać z javascript Object.defineProperty

183

Rozejrzałem się, jak użyć tej Object.definePropertymetody, ale nie znalazłem nic przyzwoitego.

Ktoś dał mi ten fragment kodu :

Object.defineProperty(player, "health", {
    get: function () {
        return 10 + ( player.level * 15 );
    }
})

Ale ja tego nie rozumiem. Głównie gettego nie mogę dostać (zamierzona gra słów). Jak to działa?

Agregat matematyczny
źródło
1
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… jest to doskonały tutorial tutaj.
Martian2049,

Odpowiedzi:

499

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ągeti setbloki:

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 iterowanefor(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żyObject.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 ustawiaconfigurable: falsewszystkie 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: falsena 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.

Jan Turoň
źródło
Ta część jest jasna. function Product(name,price) { this.name = name; this.price = price; var _discount; // private member Object.defineProperty(this,"discount",{ get: function() { return _discount; }, set: function(value) { _discount = value; if(_discount>80) _discount = 80; } }); } 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
abu abu,
27

getto funkcja wywoływana podczas próby odczytania wartości player.health, na przykład w:

console.log(player.health);

W rzeczywistości niewiele różni się od:

player.getHealth = function(){
  return 10 + this.level*15;
}
console.log(player.getHealth());

Ustawione jest przeciwieństwo get, które będzie użyte podczas przypisywania do wartości. Ponieważ nie ma setera, wydaje się, że przypisywanie do zdrowia gracza nie jest zamierzone:

player.health = 5; // Doesn't do anything, since there is no set function defined

Bardzo prosty przykład:

var player = {
  level: 5
};

Object.defineProperty(player, "health", {
  get: function() {
    return 10 + (player.level * 15);
  }
});

console.log(player.health); // 85
player.level++;
console.log(player.health); // 100

player.health = 5; // Does nothing
console.log(player.health); // 100

Paweł
źródło
to tylko funkcja, której nie trzeba właściwie ()wywoływać ... Nie rozumiem, jaki był pomysł, kiedy to wymyślili. Funkcje są całkowicie takie same: jsbin.com/bugipi/edit?js,console,output
vsync
15

Zdefiniuj właściwość to metoda na Object, która pozwala skonfigurować właściwości w celu spełnienia niektórych kryteriów. Oto prosty przykład z obiektem pracownika z dwiema właściwościami FirstName i lastName i dołącz te dwie właściwości, zastępując metodę toString na obiekcie.

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
employee.toString=function () {
    return this.firstName + " " + this.lastName;
};
console.log(employee.toString());

Otrzymasz Output jako: Jameel Moideen

Zamierzam zmienić ten sam kod, używając definicji obiektu na obiekcie

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
Object.defineProperty(employee, 'toString', {
    value: function () {
        return this.firstName + " " + this.lastName;
    },
    writable: true,
    enumerable: true,
    configurable: true
});
console.log(employee.toString());

Pierwszy parametr to nazwa obiektu, a następnie drugi parametr to nazwa dodawanej właściwości, w naszym przypadku toString, a następnie ostatni parametr to obiekt json, którego wartość będzie funkcją, a trzy parametry do zapisu, zliczalne i konfigurowalne. Teraz właśnie zadeklarowałem wszystko jako prawdziwe.

Jeśli uruchomisz przykład, otrzymasz Output jako: Jameel Moideen

Rozumiemy, dlaczego potrzebujemy trzech właściwości, takich jak zapis, możliwość wyliczenia i konfigurowalność.

zapisywalny

Jedną z bardzo irytujących części javascript jest, jeśli zmienisz właściwość toString na coś innego, na przykład

wprowadź opis zdjęcia tutaj

jeśli uruchomisz to ponownie, wszystko się zepsuje. Zmieńmy zapisywalny na false. Jeśli uruchomisz to samo ponownie, otrzymasz poprawne wyjście jako „Jameel Moideen”. Ta właściwość zapobiegnie późniejszemu zastąpieniu tej właściwości.

wyliczalny

jeśli wydrukujesz wszystkie klucze wewnątrz obiektu, możesz zobaczyć wszystkie właściwości, w tym toString.

console.log(Object.keys(employee));

wprowadź opis zdjęcia tutaj

jeśli ustawisz element enumerable na false, możesz ukryć właściwość toString przed wszystkimi innymi. Jeśli uruchomisz to ponownie, otrzymasz FirstName, lastName

konfigurowalny

jeśli ktoś później ponownie zdefiniował obiekt później, na przykład wyliczalny na true i uruchom go. Możesz zobaczyć, że właściwość toString pojawiła się ponownie.

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
Object.defineProperty(employee, 'toString', {
    value: function () {
        return this.firstName + " " + this.lastName;
    },
    writable: false,
    enumerable: false,
    configurable: true
});

//change enumerable to false
Object.defineProperty(employee, 'toString', {

    enumerable: true
});
employee.toString="changed";
console.log(Object.keys(employee));

wprowadź opis zdjęcia tutaj

możesz ograniczyć to zachowanie, ustawiając konfigurowalne na false.

Oryginalne odniesienie do tych informacji pochodzi z mojego osobistego bloga

Code-EZ
źródło
1
Rozumiem, że miałeś to na swoim blogu i właśnie wkleiłeś tutaj, ale przynajmniej wiem to na przyszłość: zrzuty ekranu nie są popularne na SO. Nie możesz skopiować kodu, aby go wypróbować, a kod nie będzie widoczny dla wyszukiwarek ani technologii wspomagających.
Domino
@JacqueGoupil Masz rację. Zaktualizuję, dodając kod zamiast zrzutu ekranu
Code-EZ
3

Zasadniczo definePropertyjest to metoda, która przyjmuje 3 parametry - obiekt, właściwość i deskryptor. W tym konkretnym wywołaniu "health"właściwość playerobiektu zostaje przypisana do 10 plus 15-krotności poziomu obiektu gracza.

Cole Pilegard
źródło
0

tak, nie ma już rozszerzania funkcji dla narzędzia ustawiającego i pobierającego to mój przykład Object.defineProperty (obj, name, func)

var obj = {};
['data', 'name'].forEach(function(name) {
    Object.defineProperty(obj, name, {
        get : function() {
            return 'setter & getter';
        }
    });
});


console.log(obj.data);
console.log(obj.name);
Faizal Pribadi
źródło
0

Object.defineProperty () jest funkcją globalną. Nie jest dostępna w funkcji, która deklaruje obiekt w inny sposób. Będziesz musiał go użyć statycznie ...

ISONecroMAn
źródło
0

Podsumowanie:

Object.defineProperty(player, "health", {
    get: function () {
        return 10 + ( player.level * 15 );
    }
});

Object.definePropertysłuży do utworzenia nowej właściwości na obiekcie gracza. Object.definePropertyjest funkcją, która jest natywnie obecna w środowisku wykonawczym JS i przyjmuje następujące argumenty:

Object.defineProperty(obj, prop, descriptor)

  1. Obiekt , na którym chcemy zdefiniować nową właściwość
  2. Nazwa nowej nieruchomości chcemy zdefiniować
  3. obiekt deskryptora

Obiekt deskryptora jest interesującą częścią. Tutaj możemy zdefiniować następujące rzeczy:

  1. konfigurowalny <boolean> : jeśli true deskryptor właściwości może zostać zmieniony, a właściwość może zostać usunięta z obiektu. Jeśli konfigurowalne falsesą przekazywane właściwości deskryptora, Object.definePropertynie można ich zmienić.
  2. Zapisywalny <boolean> : jeśli truewłaściwość może zostać zastąpiona za pomocą operatora przypisania.
  3. Wyliczalny <boolean> : jeśli true właściwość może być iterowana w for...inpętli. Również podczas korzystania z Object.keysfunkcji przycisk będzie obecny. Jeśli właściwość jest false, nie będą iterowane przy użyciu for..inpętli i nie będą wyświetlane podczas używania Object.keys.
  4. get <function> : wymagana jest funkcja wywoływana za każdym razem, gdy właściwość jest wymagana. Zamiast podawać wartość bezpośrednią wywoływana jest ta funkcja, a zwracana wartość jest podawana jako wartość właściwości
  5. set <function> : funkcja wywoływana za każdym razem, gdy właściwość jest przypisana. Zamiast ustawiania wartości bezpośredniej wywoływana jest ta funkcja, a zwracana wartość służy do ustawiania wartości właściwości.

Przykład:

const player = {
  level: 10
};

Object.defineProperty(player, "health", {
  configurable: true,
  enumerable: false,
  get: function() {
    console.log('Inside the get function');
    return 10 + (player.level * 15);
  }
});

console.log(player.health);
// the get function is called and the return value is returned as a value

for (let prop in player) {
  console.log(prop);
  // only prop is logged here, health is not logged because is not an iterable property.
  // This is because we set the enumerable to false when defining the property
}

Willem van der Veen
źródło
0

import { CSSProperties } from 'react'
import { BLACK, BLUE, GREY_DARK, WHITE } from '../colours'

export const COLOR_ACCENT = BLUE
export const COLOR_DEFAULT = BLACK
export const FAMILY = "'Segoe UI', sans-serif"
export const SIZE_LARGE = '26px'
export const SIZE_MEDIUM = '20px'
export const WEIGHT = 400

type Font = {
  color: string,
  size: string,
  accent: Font,
  default: Font,
  light: Font,
  neutral: Font,
  xsmall: Font,
  small: Font,
  medium: Font,
  large: Font,
  xlarge: Font,
  xxlarge: Font
} & (() => CSSProperties)

function font (this: Font): CSSProperties {
  const css = {
    color: this.color,
    fontFamily: FAMILY,
    fontSize: this.size,
    fontWeight: WEIGHT
  }
  delete this.color
  delete this.size
  return css
}

const dp = (type: 'color' | 'size', name: string, value: string) => {
  Object.defineProperty(font, name, { get () {
    this[type] = value
    return this
  }})
}

dp('color', 'accent', COLOR_ACCENT)
dp('color', 'default', COLOR_DEFAULT)
dp('color', 'light', COLOR_LIGHT)
dp('color', 'neutral', COLOR_NEUTRAL)
dp('size', 'xsmall', SIZE_XSMALL)
dp('size', 'small', SIZE_SMALL)
dp('size', 'medium', SIZE_MEDIUM)

export default font as Font

Alvin Smith
źródło
0

Definiuje nową właściwość bezpośrednio na obiekcie lub modyfikuje istniejącą właściwość na obiekcie i zwraca obiekt.

Uwaga: Metodę tę wywołujesz bezpośrednio w Konstruktorze obiektów, a nie w instancji typu Object.

   const object1 = {};
   Object.defineProperty(object1, 'property1', {
      value: 42,
      writable: false, //If its false can't modify value using equal symbol
      enumerable: false, // If its false can't able to get value in Object.keys and for in loop
      configurable: false //if its false, can't able to modify value using defineproperty while writable in false
   });

wprowadź opis zdjęcia tutaj

Proste objaśnienie definicji właściwości.

Przykładowy kod: https://jsfiddle.net/manoj_antony32/pu5n61fs/

Mano
źródło
0

Object.defineProperty(Array.prototype, "last", {
  get: function() {
    if (this[this.length -1] == undefined) { return [] }
    else { return this[this.length -1] }
  }
});

console.log([1,2,3,4].last) //returns 4

Jaeyson Anthony Y.
źródło