Статьи

Модули и пространства имен в JavaScript

JavaScript не поставляется с поддержкой модулей. В этом блоге рассматриваются шаблоны и API, которые предоставляют такую ​​поддержку. Он разделен на следующие части:

  1. Шаблоны для структурирования модулей.
  2. API для загрузки модулей асинхронно.
  3. Связанное чтение, фон и источники.

1. Шаблоны для структурирования модулей

Модуль выполняет две цели: во-первых, он содержит контент, сопоставляя идентификаторы со значениями. Во-вторых, он обеспечивает пространство имен для этих идентификаторов, чтобы они не конфликтовали с идентификаторами в других модулях. В JavaScript модули реализуются через объекты.

  • Пространство имен: модуль верхнего уровня помещается в глобальную переменную. Эта переменная является пространством имен содержимого модуля.
  • Хранение содержимого: каждое свойство модуля содержит значение.
  • Вложенные модули: один достигает вложенности, помещая модуль в другой.

Наполнение модуля контентом

Подход 1: Объектный литерал.

    var namespace = {
        func: function() { ... },
        value: 123
    };

Подход 2: Присвоение свойств.

    var namespace = {};
namespace.func = function() { ... };
namespace.value = 123;

Доступ к контенту в любом из подходов:

    namespace.func();
    console.log(namespace.value + 44);

Оценка:

  1. Объект буквальный.

    • Pro: элегантный синтаксис.
    • Против: как единственная, иногда очень длинная синтаксическая конструкция, она накладывает ограничения на ее содержимое. Необходимо поддерживать открывающую скобку перед содержимым и закрывающую скобку после содержимого. И нужно помнить, чтобы не добавлять запятую после последнего значения свойства. Это усложняет перемещение контента.
  2. Присвоение свойств.

    • Con: избыточные повторения идентификатора пространства имен.

Шаблон модуля: личные данные и инициализация

В шаблоне модуля используется выражение функции немедленного вызова (IIFE, [1]), чтобы присоединить среду к данным модуля. Доступ к привязкам внутри этой среды возможен из модуля, но не снаружи. Другое преимущество состоит в том, что IIFE дает вам место для инициализации.

    var namespace = function() {
        // set up private data
        var arr = []; // not visible outside
        for(var i=0; i<4; i++) {
            arr.push(i);
        }
        return {
            // read-only access via getter
            get values() {
                return arr;
            }
        };
    }();
    console.log(namespace.values); // [0,1,2,3]

Комментарии:

  • Con: Труднее читать и сложнее понять, что происходит.
  • Con: труднее исправить. Время от времени вы можете повторно использовать существующий код, просто немного его исправив. Да, это нарушает инкапсуляцию, но также может быть очень полезно для временных решений. Шаблон модуля делает такое исправление невозможным (что может быть особенностью, в зависимости от вашего вкуса).
  • Альтернатива для личных данных: используйте соглашение об именах для частных свойств, например, все свойства, имена которых начинаются с подчеркивания, являются частными.

Вариация: Пространство имен является параметром функции.

    var namespace = {};
    (function(ns) {
        // (set up private data here)
        ns.func = function() { ... };
        ns.value = 123;
    }(namespace));

Вариация: это как идентификатор пространства имен (не может быть назначен случайно).

    var namespace = {};
    (function() {
        // (set up private data here)
        this.func = function() { ... };
        this.value = 123;
    }).call(namespace); // hand in implicit parameter "this"

Ссылаясь на свойства родного брата

Использовать это. Con: скрыто, если вы вкладываете функции (которые включают методы во вложенных объектах).

    var namespace = {
        _value: 123; // private via naming convention
        getValue: function() {
            return this._value;
        }
        anObject: {
            aMethod: function() {
                // "this" does not point to the module here
            }
        }
    }

Глобальный доступ. Минусы: затрудняет переименование пространства имен, многословно для вложенных пространств имен.

    var namespace = {
        _value: 123; // private via naming convention
        getValue: function() {
            return namespace._value;
        }
    }

Пользовательский идентификатор: Шаблон модуля (см. Выше) позволяет использовать пользовательский локальный идентификатор для ссылки на текущий модуль.

  • Шаблон модуля с литералом объекта: назначьте объект локальной переменной перед его возвратом.
  • Шаблон модуля с параметром: параметр является пользовательским идентификатором.

Частные данные и инициализация для свойств

IFEE может использоваться для прикрепления личных данных и кода инициализации к объекту. Это может сделать то же самое для одного свойства.

    var ns = {
        getValue: function() {
            var arr = []; // not visible outside
            for(var i=0; i<4; i++) {
                arr.push(i);
            }
            return function() { // actual property value
                return arr;
            };
        }()
    };

Продолжайте читать для применения этого образца.

Типы в объектных литералах

Проблема: тип JavaScript определяется в два этапа. Сначала определите конструктор. Во-вторых, настройте прототип конструктора. Эти два шага не могут быть выполнены в объектных литералах. Есть два решения:

  • Используйте API наследования, где конструктор и прототип могут быть определены одновременно [4].
  • Оберните две части типа в IIFE:
        var ns = {
            Type: function() {
                var constructor = function() {
                    // ...
                };
                constructor.prototype = {
                    // ...
                };
                return constructor; // value of Type
            }()
        };
    

Управление пространствами имен

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

    var namespace = namespace || {};

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

    var topns = topns || {};
    topns.module1 = {
        // content
    }
    topns.module2 = {
        // content
    }

YUI2 использует следующий шаблон для создания вложенных пространств имен.

    YAHOO.namespace("foo.bar");
    YAHOO.foo.bar.doSomething = function() { ... };

2. API для загрузки модулей асинхронно

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

  1. Файл загружен.
  2. Файл интерпретируется.

Все браузеры блокируют обработку последующего содержимого до тех пор, пока (2) не будет завершено, потому что все является однопоточным и должно обрабатываться по порядку. Более новые браузеры выполняют некоторые загрузки параллельно, но рендеринг по-прежнему заблокирован [2]. Это излишне задерживает первоначальное отображение страницы. Современные модульные API предоставляют способ обойти это, поддерживая асинхронную загрузку модулей. Обычно использование таких API состоит из двух частей: первая определяет, какие модули вы хотели бы использовать. Во-вторых, один обеспечивает обратный вызов, который вызывается, когда все модули готовы. Целью этого раздела является не полное введение, а скорее представление о том, что возможно в пространстве разработки модулей JavaScript.

2.1. RequireJS

RequireJS был создан как стандарт для модулей, которые работают как на серверах, так и в браузерах.
Сайт RequireJS объясняет взаимосвязь между RequireJS и ранее CommonJS стандартной для серверных модулей [3]:


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

RequireJS пытается придерживаться духа CommonJS, используя строковые имена для ссылки на зависимости и избегая модулей, определяющих глобальные объекты, но все же позволяет кодировать формат модуля, который хорошо работает в браузере. RequireJS реализует предложение определения асинхронного модуля (ранее Transport / C).

Если у вас есть модули в традиционном формате модулей CommonJS, вы можете легко конвертировать их для работы с RequireJS.

Проекты RequireJS имеют следующую файловую структуру:

    project-directory/
        project.html
        legacy.js
        scripts/
            main.js
            require.js
            helper/
                util.js

project.html:

    <!DOCTYPE html>
    <html>
        <head>
            <title>My Sample Project</title>
            <!-- data-main attribute tells require.js to load
                 scripts/main.js after require.js loads. -->
            <script data-main="scripts/main" src="scripts/require.js"></script>
        </head>
        <body>
            <h1>My Sample Project</h1>
        </body>
    </html>

main.js: helper / util разрешается относительно data-main. legacy.js заканчивается на .js и предполагается, что он не в формате модуля. Следствием этого является то, что его путь разрешен относительно project.html и что отсутствует параметр функции для доступа к его содержимому (модулю).

    require(["helper/util", "legacy.js"], function(util) {
        //This function is called when scripts/helper/util.js is loaded.

        require.ready(function() {
            //This function is called when the page is loaded
            //(the DOMContentLoaded event) and when all required
            //scripts are loaded.
        });
    });

Другие особенности RequireJS:

  • Укажите и используйте данные интернационализации.
  • Загрузка текстовых файлов (например, для использования в шаблонах HTML)
  • Используйте результаты службы JSONP для начальной настройки приложения.

2.2. YUI3

Версия 3 фреймворка YUI JavaScript содержит собственную модульную инфраструктуру. Модули YUI3 загружаются асинхронно. Общая схема их использования заключается в следующем.

    YUI().use('dd', 'anim', function(Y) {
        // Y.DD is available
        // Y.Anim is available
    });

шаги:

  • Укажите идентификаторы «dd» и «anim» для модулей, которые вы хотите загрузить.
  • Обеспечить обратный вызов, который будет вызван после загрузки всех модулей.
  • Параметр Y обратного вызова является пространством имен YUI. Это пространство имен содержит подпространства имен DD и Anim для модулей. Как видите, идентификатор модуля и его пространство имен обычно различаются.

Метод YUI.add () позволяет регистрировать ваши собственные модули.

    YUI.add('mymodules-mod1',
        function(Y) {
            Y.namespace('mynamespace');
            Y.mynamespace.Mod1 = function() {
                // expose an API
            };
        },
        '0.1.1' // module version
    );

YUI включает загрузчик для извлечения модулей из внешних файлов. Он настраивается через параметр API. В следующем примере загружаются два модуля: встроенный модуль YUI dd и внешний модуль yui_flot, доступный в режиме онлайн.

    YUI({
        modules:  {
            yui2_yde_datasource: { // not used below
                fullpath: 'http://yui.yahooapis.com/datasource-min.js'
            },
            yui_flot: {
                fullpath: 'http://bluesmoon.github.com/yui-flot/yui.flot.js'
            }
        }
    }).use('dd', 'yui_flot', function(Y) {
        // do stuff
    });

2,3. Скриптовые загрузчики

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

  • LABjs: относительно простой загрузчик скриптов. Используйте его вместо RequireJS, если вам нужно загружать скрипты в точном порядке и вам не нужно управлять зависимостями модуля. Предпосылки: « LABjs & RequireJS: загрузка ресурсов JavaScript в увлекательной игровой форме» описывает различия между LABjs и RequireJS.
  • yepnope : быстрый загрузчик скриптов, который позволяет загружать некоторые скрипты в зависимости от возможностей веб-браузера.

3. Связанное чтение, справочная информация и источники

Связанное чтение:

Фон:

  1. Область видимости переменной JavaScript и ее подводные камни
  2. Загрузка скриптов без блокировки
  3. CommonJS Модули
  4. Облегченные API наследования JavaScript

Основные источники этого поста:

 

С http://www.2ality.com/2011/04/modules-and-namespaces-in-javascript.html