Moderní tvorba webových aplikací
O webu

Object.defineProperty v JavaScriptu

Jak pomocí Object.defineProperty definovat vlastnosti objektů s přesnou kontrolou nad jejich chováním.

13 minut

Metoda Object.defineProperty umožňuje definovat novou vlastnost objektu nebo upravit existující s přesnou kontrolou nad jejím chováním. Na rozdíl od přímého přiřazení (obj.prop = hodnota) můžete nastavit, zda je vlastnost zapisovatelná, enumerovatelná nebo konfigurovatelná.

Základní syntaxe

Object.defineProperty(objekt, nazevVlastnosti, deskriptor);

Metoda vrací původní objekt s přidanou nebo upravenou vlastností.

Deskriptor vlastnosti

Deskriptor je objekt, který popisuje chování vlastnosti. Existují dva typy deskriptorů:

Datový deskriptor

Object.defineProperty(obj, "jmeno", {
  value: "Jan",           // hodnota vlastnosti
  writable: true,         // lze měnit hodnotu?
  enumerable: true,       // zobrazí se ve for...in?
  configurable: true      // lze smazat nebo změnit deskriptor?
});

Přístupový deskriptor (getter/setter)

Object.defineProperty(obj, "jmeno", {
  get() { return this._jmeno; },
  set(hodnota) { this._jmeno = hodnota; },
  enumerable: true,
  configurable: true
});

Pozor: Datový a přístupový deskriptor nelze kombinovat. Vlastnost má buď value/writable, nebo get/set.

Výchozí hodnoty

Při použití Object.defineProperty jsou výchozí hodnoty restriktivní:

// Toto vytvoří vlastnost, kterou nelze změnit ani smazat
Object.defineProperty(obj, "konstanta", {
  value: 42
});

// Ekvivalent:
Object.defineProperty(obj, "konstanta", {
  value: 42,
  writable: false,      // výchozí
  enumerable: false,    // výchozí
  configurable: false   // výchozí
});

Oproti tomu přímé přiřazení nastaví všechny příznaky na true:

obj.prop = 42;

// Ekvivalent:
Object.defineProperty(obj, "prop", {
  value: 42,
  writable: true,
  enumerable: true,
  configurable: true
});

Praktické příklady

Konstantní vlastnost

const config = {};

Object.defineProperty(config, "API_URL", {
  value: "https://api.example.com",
  writable: false,
  enumerable: true,
  configurable: false
});

config.API_URL = "https://jina.url"; // V strict mode vyhodí TypeError
console.log(config.API_URL);         // "https://api.example.com"

Skrytá vlastnost

Vlastnost s enumerable: false se nezobrazí ve for...in, Object.keys() ani JSON.stringify():

const uzivatel = { jmeno: "Jan" };

Object.defineProperty(uzivatel, "_id", {
  value: 12345,
  enumerable: false
});

console.log(Object.keys(uzivatel)); // ["jmeno"]
console.log(uzivatel._id);          // 12345
console.log(JSON.stringify(uzivatel)); // '{"jmeno":"Jan"}'

Computed property (getter)

const kruh = { polomer: 5 };

Object.defineProperty(kruh, "obsah", {
  get() {
    return Math.PI * this.polomer ** 2;
  },
  enumerable: true
});

console.log(kruh.obsah);  // 78.54...
kruh.polomer = 10;
console.log(kruh.obsah);  // 314.15...

Validace při přiřazení (setter)

const osoba = { _vek: 0 };

Object.defineProperty(osoba, "vek", {
  get() {
    return this._vek;
  },
  set(hodnota) {
    if (typeof hodnota !== "number" || hodnota < 0) {
      throw new Error("Věk musí být nezáporné číslo");
    }
    this._vek = hodnota;
  },
  enumerable: true
});

osoba.vek = 25;           // OK
osoba.vek = -5;           // Error: Věk musí být nezáporné číslo

Reálné příklady z praxe

Deprecation warning

Označení vlastnosti jako zastaralé — uživatel dostane varování, ale kód stále funguje:

function deprecate(obj, prop, novyNazev) {
  const hodnota = obj[prop];
  Object.defineProperty(obj, prop, {
    get() {
      console.warn(`${prop} je zastaralé, použijte ${novyNazev}`);
      return hodnota;
    },
    enumerable: false
  });
}

const config = { apiUrl: "https://api.example.com" };
config.API_URL = config.apiUrl; // stará verse
deprecate(config, "API_URL", "apiUrl");

config.API_URL; // Warning: API_URL je zastaralé, použijte apiUrl

Lazy loading (memoisace)

Hodnota se vypočítá až při prvním přístupu a pak se uloží:

function lazy(obj, prop, vypocet) {
  Object.defineProperty(obj, prop, {
    get() {
      const hodnota = vypocet();
      // Přepíše getter na prostou hodnotu
      Object.defineProperty(obj, prop, {
        value: hodnota,
        writable: false,
        enumerable: true
      });
      return hodnota;
    },
    configurable: true,
    enumerable: true
  });
}

const modul = {};
lazy(modul, "tezkéData", () => {
  console.log("Načítám data...");
  return { /* ... velký objekt ... */ };
});

// Nic se nenačítá, dokud nepřistoupíme
console.log(modul.tezkéData); // "Načítám data..." + vrátí objekt
console.log(modul.tezkéData); // Vrátí objekt bez logování (už je uložené)

Polyfill pro chybějící metodu

Přidání metody do prototypu s korektními příznaky:

// Polyfill pro Array.prototype.at (před ES2022)
if (!Array.prototype.at) {
  Object.defineProperty(Array.prototype, "at", {
    value: function(index) {
      if (index < 0) index = this.length + index;
      return this[index];
    },
    writable: true,
    enumerable: false,  // Nesmí se objevit ve for...in
    configurable: true
  });
}

[1, 2, 3].at(-1); // 3

Ochrana globálních objektů

Zabránění přepsání důležitých funkcí (např. v security kontextu):

// Zamknutí console.log proti přepsání
Object.defineProperty(console, "log", {
  value: console.log,
  writable: false,
  configurable: false
});

console.log = function() {}; // TypeError v strict mode, tiše selže jinak
console.log("Stále funguje!"); // Funguje

Sledování změn (debugging)

Zjištění, kdo a kdy mění hodnotu:

function sleduj(obj, prop) {
  let hodnota = obj[prop];
  Object.defineProperty(obj, prop, {
    get() { return hodnota; },
    set(nova) {
      console.log(`${prop}: ${hodnota} → ${nova}`);
      console.trace(); // Zobrazí call stack
      hodnota = nova;
    }
  });
}

const state = { count: 0 };
sleduj(state, "count");

state.count = 1; // "count: 0 → 1" + stack trace
state.count = 2; // "count: 1 → 2" + stack trace

Reaktivita ve Vue 2

Vue 2 používal Object.defineProperty jako základ svého reaktivního systému. Zjednodušená verse toho, co framework dělá interně:

function defineReactive(obj, key) {
  let value = obj[key];
  const subscribers = [];  // komponenty závislé na této vlastnosti

  Object.defineProperty(obj, key, {
    get() {
      // Při renderování komponenty ji zaregistruj jako závislost
      if (currentlyRenderingComponent) {
        subscribers.push(currentlyRenderingComponent);
      }
      return value;
    },
    set(newValue) {
      value = newValue;
      // Při změně překresli všechny závislé komponenty
      subscribers.forEach(component => component.update());
    }
  });
}

// Vue 2 toto volá pro každou vlastnost v data()
function observe(obj) {
  for (const key of Object.keys(obj)) {
    defineReactive(obj, key);
  }
}

Tento přístup měl známá omezení:

// Nefungovalo reaktivně — vlastnost neexistovala při observe()
this.novaVlastnost = "hodnota";

// Workaround
Vue.set(this, "novaVlastnost", "hodnota");

// Nefungovalo reaktivně — index pole není "vlastnost"
this.pole[0] = "nova";

// Workaround
Vue.set(this.pole, 0, "nova");

Vue 3 přešel na Proxy, který tyto problémy nemá — zachytí i nové vlastnosti a přístup přes index. Právě limity Object.defineProperty byly hlavním důvodem přechodu.

Definice více vlastností najednou

Pro definici více vlastností použijte Object.defineProperties:

const obj = {};

Object.defineProperties(obj, {
  jmeno: {
    value: "Jan",
    writable: true,
    enumerable: true
  },
  vek: {
    value: 30,
    writable: true,
    enumerable: true
  },
  _tajne: {
    value: "skryto",
    enumerable: false
  }
});

Zjištění deskriptoru

Deskriptor existující vlastnosti zjistíte pomocí Object.getOwnPropertyDescriptor:

const obj = { jmeno: "Jan" };

const deskriptor = Object.getOwnPropertyDescriptor(obj, "jmeno");
console.log(deskriptor);
// {
//   value: "Jan",
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

Monkey-patching knihoven

Častý use case pro Object.defineProperty je úprava chování existujícího kódu — například knihovny třetí strany, kterou nemůžete přímo upravit:

// Přidání logování do existující metody knihovny
const puvodniFetch = window.fetch;

Object.defineProperty(window, "fetch", {
  value: function(...args) {
    console.log("Fetch volán s:", args[0]);
    return puvodniFetch.apply(this, args);
  },
  writable: true,
  configurable: true
});

Nebo sledování přístupu k vlastnosti:

// Zachycení, kdy někdo čte localStorage
const puvodniStorage = window.localStorage;

Object.defineProperty(window, "localStorage", {
  get() {
    console.log("Přístup k localStorage");
    return puvodniStorage;
  },
  configurable: true
});

Pozor: Monkey-patching je mocný nástroj, ale snadno vede k těžko laditelným chybám. Používejte opatrně a pouze tam, kde není jiná možnost.

Srovnání s Proxy

Proxy je modernější alternativa (ES6+), která umožňuje zachytit jakoukoliv operaci nad objektem:

// Object.defineProperty — musíte definovat každou vlastnost zvlášť
const obj1 = {};
Object.defineProperty(obj1, "a", {
  get() { console.log("čtení a"); return this._a; },
  set(v) { console.log("zápis a"); this._a = v; }
});

// Proxy — zachytí všechny vlastnosti najednou
const obj2 = new Proxy({}, {
  get(target, prop) {
    console.log(`čtení ${prop}`);
    return target[prop];
  },
  set(target, prop, value) {
    console.log(`zápis ${prop}`);
    target[prop] = value;
    return true;
  }
});

Kdy použít co

Object.defineProperty Proxy
Známé vlastnosti Ano Ano
Dynamické vlastnosti Ne (musíte znát název předem) Ano (zachytí cokoliv)
Úprava existujícího objektu Ano (in-place) Ne (vytváří wrapper)
Zachycení delete, in, … Ne Ano
Podpora IE11 Ano Ne
Výkon Rychlejší Pomalejší (režie trapu)

Proxy je lepší volba pro reaktivní systémy (Vue 3), validaci vstupů, nebo když potřebujete zachytit operace nad neznámými vlastnostmi. defineProperty je lepší pro úpravu konkrétních vlastností existujících objektů (monkey-patching) nebo když potřebujete podporu starších prohlížečů.

Jednodušší alternativy

Pro běžné případy existují jednodušší přístupy:

Getter/setter v objektovém literálu

const kruh = {
  polomer: 5,
  get obsah() {
    return Math.PI * this.polomer ** 2;
  }
};

Getter/setter ve třídě

class Kruh {
  constructor(polomer) {
    this.polomer = polomer;
  }

  get obsah() {
    return Math.PI * this.polomer ** 2;
  }
}

Object.freeze pro neměnnost

const config = Object.freeze({
  API_URL: "https://api.example.com",
  TIMEOUT: 5000
});

config.API_URL = "jina"; // Tiše selže (v strict mode TypeError)
config.NOVA = "x";       // Tiše selže

Kdy je to antipattern

Gettery a settery s Object.defineProperty umožňují vytvořit „magické” objekty — vypadají jako běžné vlastnosti, ale na pozadí spouští libovolný kód. To může být problém:

// Tohle vypadá nevinně...
user.email = "test@example.com"

// ...ale spustilo validaci, API call, toast notifikaci a analytics event

Problematické použití

  • Skryté side-effecty — přiřazení vypadá triviálně, ale dělá netriviální věci
  • Těžko debugovatelné — když obj.x = 5 nefunguje, je těžké zjistit proč
  • Porušení principu nejmenšího překvapení — kolega netuší, že jednoduchý assignment má vedlejší efekty

Kdy je to v pořádku

  • Framework s jasnou konvencí — Vue, Svelte, MobX — všichni vědí, že reaktivní state „dělá věci”
  • Dobře zdokumentované API — název funkce jasně říká, co objekt dělá (např. createReactiveStore())
  • Interní implementace — uživatel knihovny přímo s gettery nepracuje
  • Computed properties bez side-effectů — getter jen počítá hodnotu z jiných vlastností

Alternativa — explicitní API

// Místo magie
params.limit = 50

// Explicitní metoda
params.set("limit", 50)
// nebo
updateParams({ limit: 50 })

Doporučení: Pro běžný aplikační kód preferujte explicitní metody. Pro knihovny a frameworky je „magie” akceptovatelná, pokud je konsistentní, zdokumentovanáočekávatelná.

Kdy použít Object.defineProperty

Hlavní síla Object.defineProperty je v tom, že vytvoříte objekt, který vypadá běžně, ale chová se „magicky”:

// Uživatel vidí toto:
params.limit = 50

// Ale pod kapotou se děje:
// setter: validace → aktualisace URL → sync do storage

Typické legitimní použití:

  • Monkey-patching — úprava chování knihoven třetích stran nebo globálních objektů
  • Skryté vlastnosti — interní stav, který nemá být vidět v JSON nebo enumeraci
  • Neměnné vlastnosti — konstanty, které nelze přepsat
  • Polyfilly — přidání chybějících metod do prototypů (např. Array.prototype.includes)
  • Dynamické gettery/settery — když názvy vlastností nejsou známé v době kompilace
  • Reaktivní systémy — Vue 2 používal defineProperty pro sledování změn

Pro většinu běžného kódu jsou objektové literály s gettery/settery, třídy nebo Proxy čitelnější a flexibilnější.

Odkazy

Novinky e-mailem

Když budu mít něco opravdu zajímavého, můžu vám to poslat e-mailem

Přidej se k 500+ čtenářům
Jen kvalitní obsah
Žádný spam

Web jecas.cz píše Bohumil Jahoda, kontakt
Seznam všech článků · Témata
2013–2026