Skip to content

Latest commit

 

History

History
165 lines (117 loc) · 8.01 KB

File metadata and controls

165 lines (117 loc) · 8.01 KB

Модули в JavaScript

Модульное программирование позволяет создавать большие программы, собирая их из отдельных модулей кода, написанных разными авторами. Главная цель модульности — инкапсуляция (скрытие) внутренних деталей реализации и поддержание чистоты глобального пространства имен, чтобы модули не могли случайно повлиять на переменные, функции и классы, определенные в других модулях.

Долгое время в JavaScript не было встроенной поддержки модулей, и разработчики использовали различные подходы для организации кода: классы, объекты и замыкания. Со временем сообщество выработало практику использования функции require(), которая стала стандартом в среде Node.js. Позже в стандарте ES6 появились ключевые слова import и export для работы с модулями.

Модули с использованием классов, объектов и замыканий

Классы как модули

Классы естественным образом работают как модули для своих методов. Например, разные классы могут иметь методы с одинаковыми именами, но это не вызывает конфликтов, поскольку каждый метод является свойством независимого объекта-прототипа.

class SingletonSet {
    has(value) { /* реализация */ }
}

class BitSet {
    has(value) { /* реализация */ }
}

Метод has() в SingletonSet не перезаписывает одноименный метод в BitSet, потому что они принадлежат разным объектам.

Объекты как пространства имен

Чтобы избежать загрязнения глобального пространства имен, можно группировать функциональность в объектах:

// Вместо множества глобальных классов
const Sets = {
    Singleton: class { /* ... */ },
    Bit: class { /* ... */ }
};

// Использование
const set = new Sets.Bit();

Этот подход похож на то, как в JavaScript организованы математические функции в объекте Math.

Скрытие реализации с помощью замыканий

Классы и объекты не позволяют полностью скрыть внутренние детали реализации. Для этого используют замыкания — возможность функции сохранять доступ к переменным из своей области видимости даже после выполнения.

Рассмотрим пример модуля для работы с битовыми множествами:

const BitSet = (function() {
    // Приватные функции и константы
    function isValid(set, n) { /* ... */ }
    function has(set, byte, bit) { /* ... */ }
    
    const BITS = new Uint8Array([1, 2, 4, 8, 16, 32, 64, 128]);
    const MASKS = new Uint8Array([~1, ~2, ~4, ~8, ~16, ~32, ~64, ~128]);
    
    // Возвращаем публичный класс
    return class BitSet extends AbstractWritableSet {
        // Реализация класса, использующая приватные функции
    };
}());

Здесь мы используем немедленно вызываемую функцию (IIFE), которая возвращает класс. Все переменные и функции, объявленные внутри IIFE, остаются приватными и недоступными извне.

Пример модуля статистики

Создадим модуль с функциями для статистических вычислений:

const stats = (function() {
    // Приватные вспомогательные функции
    const sum = (x, y) => x + y;
    const square = x => x * x;
    
    // Публичные функции
    function mean(data) {
        return data.reduce(sum) / data.length;
    }
    
    function stddev(data) {
        let m = mean(data);
        return Math.sqrt(
            data.map(x => x - m).map(square).reduce(sum) / (data.length - 1)
        );
    }
    
    // Экспортируем публичный API
    return { mean, stddev };
}());

// Использование
stats.mean([1, 3, 5, 7, 9]); // 5
stats.stddev([1, 3, 5, 7, 9]); // Math.sqrt(10)

В этом примере функции sum() и square() являются внутренними деталями реализации и недоступны извне модуля.

Автоматизация модульности на основе замыканий

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

  1. Берет несколько файлов с кодом
  2. Оборачивает каждый файл в IIFE
  3. Отслеживает, какие значения должны быть экспортированы
  4. Объединяет все в один большой файл

Пример результата работы такого инструмента:

const modules = {};

function require(moduleName) {
    return modules[moduleName];
}

modules["sets.js"] = (function() {
    const exports = {};
    
    // Содержимое файла sets.js
    exports.BitSet = class BitSet { /* ... */ };
    
    return exports;
}());

modules["stats.js"] = (function() {
    const exports = {};
    
    // Содержимое файла stats.js
    const sum = (x, y) => x + y;
    const square = x => x * x;
    
    exports.mean = function(data) { /* ... */ };
    exports.stddev = function(data) { /* ... */ };
    
    return exports;
}());

Теперь мы можем использовать эти модули:

// Получаем ссылки на нужные модули
const stats = require("stats.js");
const BitSet = require("sets.js").BitSet;

// Используем модули
let s = new BitSet(100);
s.insert(10);
s.insert(20);
s.insert(30);
let average = stats.mean([...s]); // 20

Этот подход лежит в основе работы современных инструментов сборки, таких как webpack и Parcel, а также напоминает систему модулей Node.js с использованием require().

Заключение

Модульность — важнейшая концепция в современной JavaScript-разработке. Понимание того, как организовать код с помощью классов, объектов и замыканий, создает прочную основу для изучения более современных систем модулей ES6 и Node.js.

Ключевые принципы модульного программирования:

  • Инкапсуляция реализации
  • Чистое глобальное пространство имен
  • Четкое определение публичного API
  • Возможность повторного использования кода