Подходы к модульной клиентской стороне Javascript без загрязнения пространства имен

Я пишу код на стороне клиента и хотел бы написать несколько модульных JS файлов, которые могут взаимодействовать, предотвращая глобальное загрязнение пространства имен.

index.html

<script src="util.js"></script>
<script src="index.js"></script>

util.js

(function() {
    var helper() {
        // Performs some useful utility operation
    }
});

index.js

(function () {
    console.log("Loaded index.js script");
    helper();
    console.log("Done with execution.");
})

Этот код прекрасно сохраняет функции утилиты в отдельном файле и не загрязняет глобальное пространство имен. Однако функция вспомогательной утилиты не будет выполняться, потому что "помощник" существует внутри отдельного пространства имен анонимных функций.

Один альтернативный подход включает размещение всего JS-кода внутри одного файла или использование одной переменной в глобальном пространстве имен следующим образом:

var util_ns = {
    helper: function() {
        // Performs some useful utility operation.        
    },
    etc.
}

Оба этих подхода имеют недостатки в отношении модульности и чистого пространства имен.

Я привык к работе (серверной стороне) в Node.js, где я могу "потребовать" один файл Javascript внутри другого, эффективно вставляя привязки util.js в пространство имен index.js.

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

Является ли это выполнимым простым способом (без библиотек и т.д.)?

Если нет, то в сфере создания JS на стороне клиента поведение больше похоже на Node и npm, я знаю о существовании попыток стандартизации requireJS, браузера, AMD и commonJS. Тем не менее, я не уверен в плюсах и минусах и фактическом использовании каждого из них.

Ответ 1

Я настоятельно рекомендую вам продолжить RequireJS.


Подход поддержки модулей (без требований/зависимостей):

// moduleA.js

var MyApplication = (function(app) {

    app.util = app.util || {};

    app.util.hypotenuse = function(a, b) {
        return Math.sqrt(a * a + b * b);
    };

    return app;
})(MyApplication || {});

// ----------

// moduleB.js

var MyApplication = (function(app) {

    app.util = app.util || {};

    app.util.area = function(a, b) {
        return a * b / 2;
    };

    return app;
})(MyApplication || {});

// ----------

// index.js - here you have to include both moduleA and moduleB manually
// or write some loader

var a = 3,
    b = 4;
console.log('Hypotenuse: ', MyApplication.util.hypotenuse(a, b));
console.log('Area: ', MyApplication.util.area(a, b));

Здесь вы создаете только одну глобальную переменную (пространство имен) MyApplication, все остальные вещи "вложены" в нее.

Fiddle - http://jsfiddle.net/f0t0n/hmbb7/


** Еще один подход, который я использовал ранее в своих проектах - https://gist.github.com/4133310 Но в любом случае я выбросил все это, когда начал использовать RequireJS. *

Ответ 2

Вы должны проверить browserify, который обработает модульный проект JavaScript в один файл. Вы можете использовать require в нем, как и в node.

Он даже дает кучу node.js libs, таких как url, http и crypto.

ДОБАВЛЕНИЕ. На мой взгляд, про браунирацию заключается в том, что он просто используется и не требует собственного кода - вы даже можете использовать уже написанный код node.js с ним. Нет никакого кода шаблона или изменения кода вообще, и он как CommonJS-совместимый как node.js. Он выводит один .js, который позволяет вам использовать require в коде вашего сайта.

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

Ответ 3

Так называемое "глобальное загрязнение пространства имен" значительно выше, чем проблема. Я не знаю о node.js, но в типичной DOM по умолчанию есть сотни глобальных переменных. Дублирование имен редко бывает проблемой, когда имена выбираются разумно. Добавление нескольких с помощью script не будет иметь ни малейшего различия. Используя шаблон, например:

var mySpecialIdentifier = mySpecialIdentifier || {};

означает добавление единственной переменной, которая может быть корнем всего вашего кода. Затем вы можете добавлять модули к вашему сердечному содержимому, например.

mySpecialIdentifier.dom = {
    /* add dom methods */
}
(function(global, undefined) {
    if (!global.mySpecialIdentifier) global.mySpecialIdentifier = {};
    /* add methods that require feature testing */
}(this));

И так далее.

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

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

Ответ 4

Я настоятельно рекомендую вам попробовать инструмент сборки.

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

Я использую GruntJS, и в основном он работает именно так. Предположим, что у вас есть ваши util.js и index.js(для чего нужно определить вспомогательный объект), как внутри js-каталога. Вы можете разрабатывать оба отдельно, а затем объединить оба файла app.js в каталог dist, который будет загружен вашим html. В Grunt вы можете указать что-то вроде:

concat: {
    app: {
        src: ['js/util.js', 'js/index.js'],
        dest: 'dist/app.js'
    }
}

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

В конце, из HTML, вам нужно будет ссылаться только на один файл:

<script src="dist/app.js"></script>

Добавление файла, находящегося в другом каталоге, очень просто:

concat: {
    app: {
        src: ['js/util.js', 'js/index.js', 'js/helpers/date/whatever.js'],
        dest: 'dist/app.js'
    }
} 

И ваш html будет ссылаться только на один файл.

Некоторые другие доступные инструменты, которые делают то же самое, Brunch и Yeoman.

-------- РЕДАКТИРОВАТЬ -----------

Требовать JS (и некоторые альтернативы, такие как Head JS) - очень популярное решение AMD (определение асинхронного модуля), которое позволяет просто указывать зависимости. С другой стороны, инструмент построения (например, Grunt) позволяет управлять файлами и добавлять дополнительные функции, не полагаясь на внешнюю библиотеку. В некоторых случаях вы можете использовать оба.

Я думаю, что проблема с файловыми зависимостями/проблемами/процессом сборки, отделенными от вашего кода, - это путь. С помощью инструментов сборки у вас есть четкое представление о вашем коде и совершенно отдельное место, где вы указываете, что делать с файлами. Он также обеспечивает очень масштабируемую архитектуру, поскольку он может работать через изменения структуры или будущие потребности (например, включая файлы LESS или CoffeeScript).

Одна последняя точка, имеющая один файл в процессе производства, также означает меньше накладных расходов HTTP. Помните, что важно минимизировать количество вызовов на сервере. Наличие нескольких файлов очень неэффективно.

Наконец, это отличная статья о инструментах сборки инструментов AMD, стоит прочитать.

Ответ 5

Вы можете сделать это следующим образом:

-- main.js --

var my_ns = {};

-- util.js --

my_ns.util = {
    map: function () {}
    // .. etc
}

-- index.js --

my_ns.index = {
    // ..
}

Таким образом, вы занимаете только одну переменную.

Ответ 6

Один из способов решения этого заключается в том, чтобы ваши компоненты разговаривали друг с другом с помощью "шины сообщений". Сообщение (или событие) состоит из категории и полезной нагрузки. Компоненты могут подписаться на сообщения определенной категории и могут публиковать сообщения. Это довольно просто реализовать, но есть и некоторые из готовых решений. Хотя это опрятное решение, оно также оказывает большое влияние на архитектуру вашего приложения.

Вот пример реализации: http://pastebin.com/2KE25Par

Ответ 7

http://brunch.io/ должен быть одним из простейших способов, если вы хотите написать node -подобный модульный код в своем браузере без async AMD ад. С его помощью вы также можете require() создавать свои шаблоны и т.д., А не только файлы JS.

Существует много скелетов (базовых приложений), которые вы можете использовать с ним и довольно зрелые.

Посмотрите пример приложения https://github.com/paulmillr/ostio, чтобы увидеть некоторую структуру. Как вы можете заметить, его написано в coffeescript, но если вы хотите писать в js, вы можете - бранч не заботится о langs.

Ответ 8

Я думаю, что вы хотите https://github.com/component/component.

Он синхронный CommonJS, как и Node.js, у него гораздо меньше накладных расходов, и это написано visionmedia, который написал connect и express.