наш блог
Наглядный пример использования паттерна «Модуль» в javascript

В отличие от большинства традиционных языков программирования, javascript не имеет модификаторов доступа, что в ряде ситуаций превращается в ощутимую проблему. Скажем, если есть желание скрыть переменные от сторонних разработчиков, а также реализовать некоторые методы класса или функции. Неудивительно, что вышеупомянутый барьер привел к появлению шаблона проектирования «Модуль», который относится к группе порождающих паттернов.
Реализовать вышеупомянутый паттерн в javascript позволяют немедленно вызываемые функции, от английского, IIFE – Immediately Invoked Function Expression. Возвращение из них объекта, свойства которого могут быть доступны тому или иному пользователю, создает замыкание.
Теперь рассмотрим детальнее:
const Module = (function() {
let counter = 0;
function increment() {
return ++counter
}
function decrement() {
return --counter
}
function getCounterVal() {
return counter
}
return {
getCounterVal,
decrement,
increment,
}
})();
console.log(Module.counter) // Выведет undefined
В примере видно, что доступ к переменной области counter закрыт именно извне, а при обращении к Module доступны только три метода: getCounterVal(), decrement() и increment().
Как известно, по умолчанию абсолютно все объекты в javascript являются мутабельными – такими, которые можно преобразовать. То есть, чтобы ограничить возможность внутреннего переопределения или добавления методов и свойств, необходимо изменить возвращаемый объект, заморозив его свойства методом Object.freeze().
return Object.freeze({
getCounterVal,
decrement,
increment
})
Вышеупомянутые манипуляции позволяют блокировать любые попытки преобразования метода возвращаемого объекта. Все они будут пресекаться либо выводом ошибки, либо игнорированием строки.
Впрочем, существует еще один важнейший нюанс. Дело в том, что Object.freeze() не работает на вложенные объекты или массивы. А значит, их по-прежнему можно изменить. Скрипт запускается командой node имя_файла.js:
const Module = (function() {
let counter = 0;
function increment() {
return ++counter
}
function decrement() {
return --counter
}
function getCounterVal() {
return counter
}
return Object.freeze({
getCounterVal,
decrement,
increment,
obj: {
a: 20
}
})();
Module.obj.a = 30;
console.log(Module.obj.a) // Выведет 30
Решить проблему позволит функция, способная заморозить все вложенные объекты, которые возвращаются модулем.
function freezeAllObject(obj) {
Object.keys(obj).map(key => {
if (typeof obj[key] === 'object' && obj[key] !== null)
freezeAllObject(obj[key])
});
return Object.freeze(obj)
}
В функции нужно пройтись по всем полям объекта. Если тип содержимого в одном из его полей окажется объектом (необходима проверка на null, так как typeof null возвращает ‘object’), то следует рекурсивно вызывать функцию freezeAllObject. В итоге она вернет замороженный объект.
Итак, если теперь предпринять попытку изменить любой вложенный объект или массив (из числа возвращаемых модулем), то произойдет один из двух вариантов – либо появится исключение TypeError, либо строка попросту будет проигнорирована.
Полный код:
function freezeAllObject(obj) {
Object.keys(obj)
.map(key => {
if (typeof obj[key] === 'object' && obj[key] !== null)
freezeAllObject(obj[key])
});
return Object.freeze(obj)
}
const Module = (function() {
let counter = 0;
function increment() {
return ++counter
}
function decrement() {
return --counter
}
function getCounterVal() {
return counter
}
return freezeAllObject({
getCounterVal,
decrement,
increment,
obj: {
a: 20
}
})
})();
console.log(Module.getCounterVal()); // Выведет 0
Module.increment();
Module.increment();
console.log(Module.getCounterVal()); // Выведет 2
Module.decrement();
console.log(Module.getCounterVal()); // Выведет 1
console.log(Module.obj.a); // Выведет 20
Module.obj.a = 30;
console.log(Module.obj.a); // Выведет 20
Как видно, использование паттерна «Модуль» позволяет создавать в объекте как приватные, так и публичные переменные и методы. А применение функции Object.freeze() делает объект иммутабельным.