Enkapsulacja danych w JavaScript

Mimo tego, że słowo kluczowe private w JavaScript jest zarezerwowane, sam język nie posiada typów prywatnych. Można mieć nadzieję, że rezerwacja nie jest bez powodu i kiedyś zasięg zmiennych zostanie zaimplementowany. Dokładnie tak jak z mechanizmem async/await.

W międzyczasie, wielu programistów stara się obejść ten problem przykładowo konwencją nazewnictwa.

let obj = {
    normalFunction: () => {
        console.log('not private');
    },
    _privateFunction: () => {
        console.log('private');
    }
};

obj.normalFunction(); // not private
obj._privateFunction(); // private

Jak widać na załączonym przykładzie, pole nie jest „prywatne”. Cała idea poziomu dostępu do pól obiektu ma na celu zamknięcie danych, tak żeby, programista używając danej instancji nie mógł nic zepsuć. Wiele osób rezygnuje więc z zabezpieczania obiektów, co oczywiście powoduje często błędy i niechęć innych programistów do tego języka.


Oczywiście nie jest tak, że w JS nie da się danych zabezpieczyć przed niechcianymi operacjami. Z uwagi na samą konstrukcję języka, pojęcie poziomu dostępu nie jest realizowane za pomocą słów kluczowych public, private czy protected. Możliwość enkapsulacji danych zapewnia za to mechanizm domknięć i zasięg zmiennych.

Domknięcie zapewnia, że obiekt zna kontekst, czyli funkcję, która powołała go do życia. W językach klasycznych zasięg zmiennej jest uzależniony od bloku kodu. W Javie, C# czy C++ dostęp do zmiennej jest możliwy tylko i wyłącznie pomiędzy nawiasami klamrowymi, w których ta zmienna została stworzona. W JavaScript aż do czasów wydania ES6 zmienne miały wyłącznie zasięg funkcji, to znaczy dostęp do zmiennej można było uzyskać gdziekolwiek w funkcji, w której ta zmienna była zainicjalizowana.

Na chwilę obecną programiści tego nietypowego języka mają możliwość stworzenia zmiennych o obu opisanych zasięgach. Słowo kluczowe var tworzy zmienną o zasięgu funkcji, a słowo let pozwala stworzyć zmienną o zasięgu blokowym.

let objectCreator = () => {
    let i = 0;

    return {
        incCounter: () => {
            i++;
        },
        getCounterValue: () => {
            return i;
        }
    };
};

let obj = objectCreator();

console.log(obj.i); // undefined
console.log(obj.getCounterValue()); // 0
obj.incCounter();
console.log(obj.getCounterValue()); // 1
obj.incCounter();
console.log(obj.getCounterValue()); // 2

console.log(obj);
// { incCounter: [Function: incCounter],
//   getCounterValue: [Function: getCounterValue] }

Powyższy kod pokazuje, że mimo tego, że obiekt obj nie posiada właściwości i, to metody tego obiektu (dzięki mechanizmowi domknięć) mogą operować na tej zmiennej.

Co więcej, jeśli w dalszej części kodu, obiekt zyska pole o takiej samej nazwie, to metody incCounter oraz getCounterValue będą korzystać z pola, które wskazał pierwotny twórca. To niezwykle eleganckie rozwiązanie.

let objectCreator = () => {
    let i = 0;

    return {
        incCounter: () => {
            i++;
        },
        getCounterValue: () => {
            return i;
        }
    };
};

let obj = objectCreator();
obj.i = 123;

console.log(obj.i); // 123
console.log(obj.getCounterValue()); // 0
obj.incCounter();
console.log(obj.getCounterValue()); // 1

Wydaje mi się, że taki sposób zabezpieczania danych jest wyjątkowo odporny na kolegów z pracy i innych programistów. Nie jest oczywiście tak estetyczny i naturalny jak użycie słowa kluczowego private, wymaga napisania dodatkowej funkcji. Warto jednak poświęcić chwilę na napisanie kodu, który jest niezniszczalny (jeśli oczywiście twórca uniknie własnych błędów, a nie uniknie :P).

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *