Структура модуля. Как разбить код на один модуль на разные js файлы?

Для шаблона модуля я делаю что-то вроде:

(function(namespace) {
    // tons of code
    // blabla
})(window.myGlobalNamespace);

Как мне разделить код? Я могу придумать несколько способов, например, использовать иерархию пространств имен или расширить объект снаружи на window.myGlobalNamespace.additionalFunc = function () {//blabla}. Каковы другие способы? Каковы плюсы и минусы? Какой из них считается лучшей практикой?

Оба из двух ответов предлагают RequireJS. Не могли бы вы объяснить, как RequireJS может решить эти проблемы:

first.js:

(function(context) {
    var parentPrivate = 'parentPrivate';
})(window.myGlobalNamespace);

second.js:

(function(context) {
    this.childFunction = console.log('trying to access parent private field: ' + parentPriavte);
}(window.myGlobalNamespace.subNamspace);

main.js:

window.myGlobalNamespace.subNamspace.childFunction(); // doesn't work

И люди могут делать

window.myGlobalNamespace.subNamspace.childFunction = function() {alert("a new function");}

чтобы изменить поведение моего кода!

Здесь есть две проблемы:

  • У нас не может быть поля, доступного для ребенка, но не для внешнего публичного доступа (т.е. защищенного). Есть ли способ достичь этого?

  • Если это не так, то есть, если мы хотим, чтобы доступ был доступен, мы должны сделать его общедоступным. Тогда пользователь сможет его изменить!

Что еще, все публичные функции могут быть изменены и заменены. Я не хочу, чтобы это произошло.

Я не вижу, как RequireJS решает эти проблемы. Может кто-то пролить свет?

Ответ 1

Есть только два способа получить JavaScript в HTML:

  • Inline - <script> some JavaScript </script>
  • Ссылка - <script src='main.js'></script>

Я знаю, что это очевидно, но нам нужна эта общая точка для следующего.;)

JavaScript не имеет возможности "импортировать" другие файлы JavaScript в него. Все "импорт" выполняется в HTML. Вы можете сделать это несколькими способами:

  • Свяжите каждый в отдельности в HMTL
  • Динамически связать их через некоторый JavaScript

    var script = document.createElement("script");
    script.src = "all.js";
    document.documentElement.firstChild.appendChild(script);
    
  • Библиотека, например RequireJS. RequireJS использует API определения асинхронного модуля (AMD). Это механизм JavaScript для определения модулей, так что модуль и его зависимости могут быть асинхронно загружены.

Это import, чтобы рассмотреть причины разделения JavaScript на отдельные файлы.

  • Поддержание работоспособности - становится проще работать на одной части за раз
  • Чтение - если все находится в одном большом файле, очень сложно понять, что есть
  • Отдел труда - проще иметь несколько разработчиков, работающих с несколькими файлами, а не с одним большим.
  • Повторное использование - все ваши функции могут быть разбиты на высокосвязные модули

Отдельные файлы JavaScript НЕ делать вещи Частные, закрытие делает вещи частными.

Теперь, рассмотрим в конце дня, когда все готово к производству, самое лучшее, что вы могли бы сделать, это оптимизировать свой JavaScript, объединив его все в один файл, так что у пользователя есть только один файл для загрузки.


При работе с частными переменными в JavaScript вы в какой-то момент захотите получить к ним доступ.

  • Общая функция - может быть изменена.
  • Привилегированная функция - открытая функция, которая может обращаться к приватной переменной.
  • Однако, если функция находится в экземпляре, тогда она может изменять только в каждом объекте.

Позвольте мне проиллюстрировать некоторый код.

module-test.html и main.js(слияние first.js, second.js и main.js для более простого тестирования)

var MODULE = (function () {
	//Private variables
	var privateParent,
	    app;
	
	privateParent = 'parentPrivate';
	
	return app = {
		//Privileged method
		getPrivateParent: function() {
			return privateParent;
		}
	};
}());

MODULE.sub = (function (parentApp) {
	//Private variables
	var childMessage,
	    Constr;
	
	childMessage = ' - trying to access parent private field: ' + parentApp.getPrivateParent();  //prints parentPrivate

	Constr = function () {
		this.childF = this.childFunction();
	};
	
	//Constructor
	Constr.prototype = {
		constructor: MODULE.sub,
		version: "1.0",
		childFunction: function () {
			$("#testing-div").append(childMessage + "</br>");
		}
	};
	return Constr;
	
}(MODULE));
	
//We could just as easily print to the console, but the 'append' allows us to display the results on the page.

$("#testing-div").append("This first part shows what <b>does not work</b>; everything is 'undefined'. " + "</br>");
$("#testing-div").append("You are unable to access the var or func directly. " + "</br>");
$("#testing-div").append("MODULE.privateParent = " + MODULE.privateParent + "</br>");
$("#testing-div").append("MODULE.app = " + MODULE.app + "</br>");
$("#testing-div").append("MODULE.sub.childMessage = " + MODULE.sub.childMessage + "</br>");
$("#testing-div").append("MODULE.sub.Constr = " + MODULE.sub.Constr + "</br>");
$("#testing-div").append("MODULE.sub.childFunction = " + MODULE.sub.childFunction + "</br>");
$("#testing-div").append("END lesson. You must access childFunction() through the <b>new</b> operator." + "</br>");
$("#testing-div").append("----------------------------------------------------" + "</br>");
	
$("#testing-div").append("Let see if making an instance of the Object works" + "</br>");
var test = new MODULE.sub();
test.childFunction(); //run the method
$("#testing-div").append("Looks like it did!!!!" + "</br>");
$("#testing-div").append("----------------------------------------------------" + "</br>");
	
$("#testing-div").append("Now let try to change the childFunction() ?" + "</br>");
test.childFunction = function() {$("#testing-div").append(" - This is a new function." + "</br>");}
test.childFunction(); // altered version
$("#testing-div").append("Looks like it was changed. :(" + "</br>");
$("#testing-div").append("----------------------------------------------------" + "</br>");
$("#testing-div").append("Does it stay changed?" + "</br>");
var test2 = new MODULE.sub();
test2.childFunction(); // doesn't work
$("#testing-div").append("NO, it was only Overriden in the 'test' Object.  It did not effect all the other new objects. :)" + "</br>");
$("#testing-div").append("----------------------------------------------------" + "</br>");
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Module Test</title>
<!-- 	<script data-main="scripts/main" src="scripts/require.js"></script> -->
</head>
<body>
    <h1>This is a test for separate Modules and Private variables.</h1>
    <div id="testing-div">
    </div>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="main.js"></script>
</body>
</html>

Ответ 2

Похоже на то, что вам нужно, это требование JS. Я использую это почти во всех своих сборках. В качестве альтернативы вы можете взглянуть на раскрывающийся шаблон модуля в качестве резервного, но для того, что вы после него звучат, как "Требование", гораздо более подходит.

http://requirejs.org

Хорошо читать: http://net.tutsplus.com/tutorials/javascript-ajax/principles-of-maintainable-javascript/

Ответ 3

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

App = window.App || {};
App.ParentClass = (function(){

   var protectedState = {
      protectedVar: 'wow such protection'
   }

   return{
      SubClassInstance: App.SubClass(protectedState), //make the subclass accessible from outside
   }
})(); //closure gives us privacy

SubClass.js

App = window.App || {};
App.SubClass = function(protectedState){

    return {
        logProtectedVar : function(){
            //can access protectedState of parent
            console.log(protectedState.protectedVar);
        }
    }
}// I require protected state to work

main.js

// protected variable is accessible from parent and subclass and nowhere else

App.ParentClass.SubClassInstance.logProtectedVar();
// 'wow such protection' 

ПРИМЕЧАНИЕ. Как заметил Чарльз У., этот шаблон работает только тогда, когда protectedState является объектом. Если бы это была строка /int, она была бы передана по значению, и изменения, сделанные в подклассе, не были бы видны из копии родителей.

Ответ 4

Модуляция (разделение кода) - это не то же самое, что защита данных (скрытие данных).

RequireJS решает проблему модуляции, а не проблему защиты данных. Или, по-другому... Независимо от того, какие проблемы существуют при попытке защитить данные и любые решения для защиты данных, эти проблемы и решения одинаковы с или без RequireJS.

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

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

У нас не может быть поля, доступного для ребенка, но не внешнего (т.е. защищенного). Есть ли способ достичь этого?

Если вам нужна модуляция (т.е. вы хотите, чтобы ребенок был закодирован отдельно от родителя), я не считаю, что это возможно в JavaScript. Было бы возможно, чтобы дети и родители работали в одном и том же закрытии, но тогда это не было бы модульным. Это верно с или без RequireJS.

Если нет, то есть, если мы хотим, чтобы доступ был доступен, мы должны сделать его общедоступным. Тогда пользователь сможет его изменить!

Если вы хотите предотвратить назначение parentPrivate, вы можете использовать Object.freeze() в пространстве имен, содержащем parentPrivate.

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

Или вы можете использовать сеттеры и геттеры, как в этом примере RequireJS:

define(function () {
    'use strict';

    var writable = "initial value";

    var namespace = {
        get unwritable() { return writable; },
        doSomething: function () { writable = "changed value"; }

    };

    return namespace;

});

Если модуль импортируется как parent, тогда parent.unwritable не может быть записано, но сам модуль все равно может изменить значение, возвращаемое письмом, на writable. Обратите внимание, что если возвращаемое значение Getter является объектом, а не примитивным значением, этот объект может быть изменен вызывающим.

Опять же, это правда, используете ли вы RequireJS или нет. Решения одинаковы, проблемы одинаковы и т.д.