Паттерн Модуль

Последнее обновление: 19.11.2023

Паттерн "Модуль" базируется на замыканиях и состоит из двух компонентов: внешняя функция, которая определяет лексическое окружение, и возвращаемый набор внутренних функций, которые имеют доступ к этому окружению.

Определим простейший модуль:

const printer = (function(){
	const messages = {greeting: "hello"};
	
	return {
		print: function(){
			console.log(messages.greeting);
		}
	}
})();
printer.print();	// hello

Здесь определена константа printer, которая представляет результат анонимной функции. Внутри подобной функции определен объект messages с некоторыми данными.

Сама анонимная функция возвращает объект, который определяет функцию print. Возвращаемый объект определяет общедоступый API, через который мы можем обращаться к данным, определенным внутри модуля.

return {
	print: function(){
		console.log(messages.greeting);
	}
}

Такая конструкция позволяет закрыть некоторый набор данных в рамках функции-модуля и опосредовать доступ к ним через определенный API - возвращаемые внутренние функции.

Возвращаемые функции могут быть определены где в другом месте, а не внутри анонимной функции:

const printer = (function(){
	const messages = {greeting: "Hello METANIT.COM"};
	const printMessage = function(){
		console.log(messages.greeting);
	};
	return {
		print: printMessage	// функция printMessage определена вне объекта
	}
})();
printer.print();	// Hello METANIT.COM

Если существует вероятность, что модуль уже определен где-то ранее в коде или во внешних подключаемых файлах, то мы можем использовать следующую конструкцию:

var printer =  printer || (function(){
	const messages = {greeting: "Hello World"};
	return {
		print: function(){
		    console.log(messages.greeting);
	    }
	}
})();
printer.print();	// Hello World

Определение var printer = printer || (function(){ ... присваивает переменной значение некоторого объекта printer, если он существует, либо присваивает результат вызова анонимной IIFE-функции. Но при таком определении мы не можем использовать ключевые слова let или const для определения объекта. Поэтому в данном случае объект определяется с помощью var.

Рассмотрим чуть более сложный пример:

const calculator = (function(){
	const data = { number: 0};
	
	return {
		sum: function(n){
			data.number += n;
		},
		subtract: function(n){
			data.number -= n;
		},
		print: function(){
			console.log("Result: ", data.number);
		}
	}
})();
calculator.sum(10);
calculator.sum(3);
calculator.display();	// Result: 13
calculator.subtract(4);
calculator.display();	// Result: 9

Данный модуль представляет примитивный калькулятор, который выполняет три операции: сложение, вычитание и вывод результата.

Все данные инкапсулированы в объекте data, который хранит результат операции. Все операции представлены тремя возвращаемыми функциями: sum, subtract и print. Через эти функции мы можем управлять результатом калькулятора извне.

Передача внешних модулей

Через параметры IIFE-функций в модули можно передать какие-нибудь данные, например, другие модули:

var moduleA = moduleA || (function () { 
    const message = "Hello World";
    return { 
        printMessage: function() {    
            console.log(message);  
        }  
     } 
})();

var moduleB = moduleB || (function (moduleA) { 
    return { 
        print: function() {    
            moduleA.printMessage();  
        }  
     } 
})(moduleA);

moduleB.print();

В данном случае модуль moduleB ожидает получение модуля moduleA. Внутри модуля moduleB идет обращение к функции moduleA.printMessage. Аналогично можно передавать и набор модулей.

Расширение модуля

При работе с модулями может возникнуть задача расширить его функционал - добавить в него функции или переменные. В этом случае мы можем использовать ряд техник.

//  первая техника
var localeModule =  localeModule || (function(locale){
	const enMessage = "Hello World";
    locale.printEn = function(){console.log(enMessage);};
	return locale;
})(localeModule || {});
// вторая техника
var localeModule =  (function(locale){
	const ruMessage = "Привет мир";
    locale.printRu = function(){console.log(ruMessage);};
	return locale;
})(localeModule);

localeModule.printEn(); // Hello World
localeModule.printRu(); // Привет мир

Для расширения модуля можно применять две техники. Первая техника заключается в том, что если модуль еще не создан, то в качестве параметра передается пустой объект:

var localeModule =  localeModule || (function(locale){
	const enMessage = "Hello World";
    locale.printEn = function(){console.log(enMessage);};
	return locale;
})(localeModule || {});

Так, в данном случае, если модуля localModule еще не существует, то будет создан объект, в который будет добавлена функция printEn для вывода некоторого сообщения.

Преимущество этой техники состоит в том, что скрипты, которые входят в модуль, могут загружаться асинхронно. Не имеет значения, какой скрипт будет загружен первым, поскольку в случае сомнений модуль будет создан заново.

Вторая техника предполагает, что модуль уже существует:

var localeModule =  (function(locale){
	const ruMessage = "Привет мир";
    locale.printRu = function(){console.log(ruMessage);};
	return locale;
})(localeModule);

Здесь мы уверены, что уже есть объект localModule, и также добавляем к нему новую функцию - printRu. В обоих случаях модуль возвращает в качестве результата расширенный новой функциональность объект из параметра.

Но независимо от того, какой тип расширения модуля применяется, у них есть один общий недостаток: функции, определенные в одном файле исходного кода для модуля, не имеют доступа к частным переменным и константам, определенным в другом файле исходного кода для того же модуля. Например, метод printRu не имеет доступа к константе enMessage.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850