Poznajemy rzecz, która wcześniej mogła nam umknąć, czyli czym są metody statyczne klasy Object takie jak seal, freeze oraz preventExtensions. Do dzieła.

Metody:

  • Object.preventExtensions(obj) – nie pozwala dodawać nowych properties
  • Object.seal(obj) – nie pozwala dodawać nowych properties ani konfigurować instniejących
  • Object.freeze(obj) – nie pozwala dodawać nowych properties, konfigurować ani zmieniać wartości już istniejących

Mamy też odpowiednio metody:

  • Object.isExtensible(obj)
  • Object.isSealed(obj)
  • Object.isFrozen(obj)

One zwracają nam bool, nazwy są chyba samoopisujące się. Tutaj mamy taki przykład z internetu, który świetnie obrazuje różnicę między sealed a frozen:

const frozen = Object.freeze({ username: 'johnsmith' });
const sealed = Object.seal({ username: 'johnsmith' });

frozen.name = 'John Smith';  // frozen = { username: 'johnsmith' }
sealed.name = 'John Smith';  // sealed = { username: 'johnsmith' }

delete frozen.username;      // frozen = { username: 'johnsmith' }
delete sealed.username;      // sealed = { username: 'johnsmith' }

frozen.username = 'jsmith';  // frozen = { username: 'johnsmith' }
sealed.username = 'jsmith';  // sealed = { username: 'jsmith' }

Pamiętać powinniśmy, że freeze działa „płytko”, posługując się terminologią kopiowania. Aby zadziałał głęboko, sami musimy sobie taką funkcję napisać, tutaj z internetu przykład podaję:

const deepFreeze = obj => {
  Object.keys(obj).forEach(prop => {
    if (typeof obj[prop] === 'object' && !Object.isFrozen(obj[prop])) deepFreeze(obj[prop]);
  });
  return Object.freeze(obj);
};

Możemy też zrobić funkcję isDeepFrozen, aczkolwiek ona zwróci true także wtedy, gdy użyjemy zwykłego freeze na obiekcie, który nie ma żadnych nested objects, to chyba też nie powinno dziwić:

const isDeepFrozen = obj =>
  Object.isFrozen(obj) &&
  Object.keys(obj).every(
    prop => typeof obj[prop] !== 'object' || isDeepFrozen(obj[prop])
  );

Kolejną ciekawą rzeczą, jaką wyszperałem w internecie jest frozenSet i zrobienie go jest zaskakująco proste:

const frozenSet = iterable => {
  const s = new Set(iterable);
  s.add = undefined;
  s.delete = undefined;
  s.clear = undefined;
  return Object.freeze(s);
};

Analogicznie możemy postąpić z map. Swoją drogą mam nadzieję, że zagadnienia „płytkości” i „głębokości” nie są nam obce oraz zdajemy sobie sprawę, że np. z Object.assign jest ten sam problem. Niektórzy rozwiązują go tak:

let student1 = {
    name: "Manish",
    company: "Gfg"

}
let student2 = JSON.parse(JSON.stringify(student1))

student1.name = "Rakesh"

console.log("student 1 name is", student1.name)
console.log("student 2 name is ", student2.name);

Pamiętać tylko musimy, że metod nie przekopiujemy w ten sposób. Ale znaleźć gdzieś dobre rozwiązanie to takie samo ćwiczenie jak je napisać, ważne abyśmy rozumieli, że mamy problem, zanim on wystąpi i umieli znaleźć dobre rozwiązanie i rozumieć, co ten kod robi oraz umieć go ocenić.

Ja na razie zostawię to nam jako zadanie domowe i pokażę, co to znaczyć sprawdzić, czy coś jest obiektem:

function isObject(item) {
  return (item && typeof item === 'object' && !Array.isArray(item) && !(item instanceof Date))
}

Zaś taki deep merge co powinien robić, to:

  • Robić object assign
  • Rozróżniać, czy coś jest typem prostym, obiektem zagnieżdżonym czy tablicą zagnieżdżoną
  • Pozwalać przekazywać więcej niż jedno źródło
  • W przypadku edge case, gdy mamy target i żadnych źródeł zwrócić target