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