Объектный литературный или модульный шаблон дизайна Javascript

Это, возможно, уже было задано много раз, и я искал вокруг SO, но пока все ответы, которые я прочитал, не совсем то, что я ищу.

Я работаю над сайтом с умеренными элементами DOM, показывающими/скрывающимися, некоторыми вызовами AJAX и, возможно, чем-то другим. Таким образом, у меня будут два основных файла script (стандарт HTML-Boilerplate)

plugins.js // third party plugins here
site.js // all my site specific code here

Раньше я использую шаблон шаблона объектного литерала, поэтому мой site.js выглядит примерно так:

var site = {
  version: '0.1',
  init: function() {
    site.registerEvents();
  },
  registerEvents: function() {
    $('.back-to-top').on('click', site.scrollToTop);
  },
  scrollToTop: function() {
    $('body').animate({scrollTop: 0}, 400);
  }
};

$(function() {
  site.init();
});

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

site.auth = {
  init: function() {
    site.auth.doms.loginButton.on('click', site.auth.events.onLoginButtonClicked);
  },
  doms: {
    loginButton: $('.login'),
    registerButton: $('.register')
  },
  events: {
    onLoginButtonClicked: function() {
    }
  },
  fbLogin: function() {
  }
};

site.dashboard = {
};

site.quiz = {
};

// more modules

Как вы можете видеть, это очень читаемо. Однако есть один очевидный недостаток, и я должен написать код типа site.auth.doms.loginButton и site.auth.events.onLoginButtonClicked. Вдруг становится трудно читать, и он будет только увеличиваться дольше, чем сложнее получить функциональность. Затем я попробовал модульный шаблон:

var site = (function() {
  function init() {
    $('.back-to-top').on('click', scrollToTop);
    site.auth.init();
  }

  function scrollToTop() {
    $('body').animate({scrollTop: 0}, 400);
  }

  return {
    init: init
  }
})();

site.auth = (function() {
  var doms = {
    loginButton: $('.login'),
    registerButton: $('.register')
  };

  function init() {
    doms.loginButton.on('click', onLoginButtonClicked);
  }

  function onLoginButtonClicked() {

  }

  return {
    init: init
  }
})();

// more modules

Как вы можете видеть, эти длинные имена исчезли, но потом, наверное, мне нужно запустить все остальные модули в функции site.init(), чтобы построить их все? Тогда я должен помнить о том, чтобы возвращать функции, которые должны быть доступны другими модулями. Оба они в порядке, я думаю, хотя и немного хлопот, но в целом, я на лучший рабочий процесс, работающий с модульным шаблоном?

Ответ 1

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

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

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

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

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

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

Способ, которым вы сейчас пользуетесь:

var module = (function () { /* ... */ return {}; }());

module.submodule = (function () { /*...*/ return {}; }());

Не лучше или хуже, чем литералы, потому что вы можете так же легко сделать это:

var module = {
    a : "",
    method : function () {},
    meta : { }
};

module.submodule = {
    a : "",
    method : function () {},
    meta : { }
};

Пока вы не нажмете что-то, что не работает для вас, работайте с тем, что наполняет ваши потребности.

Лично я обычно создаю любые объекты только для данных как литералы: config-objects, объекты, которые входят в другие соединения и т.д.

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

// ex:
var rectangle = {
    width  : 12,
    height : 24,
    area : 0,
    perimeter : 0,
    init_area : function () { this.area = this.width * this.height; return this; }, // buh...
    init_perimeter : function () { this.perimeter = (this.width * 2) + (this.height * 2); return this; } // double-buh...
}.init_area().init_perimeter();

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

var rectangle = (function (width, height) {
    var public_interface = {
        width  : width,
        height : height,
        area   : width * height,
        perimeter : (2 * width) + (2 * height)
    };
    return public_interface;
}(12, 24));

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

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

... но если у вас есть интерфейс, который выглядит так:

MyApp.myServices.webService.send();

и он ожидает найти:

MyApp.appData.user.tokens.latest;  // where personally, I might leave tokens in a closure

Если вы измените структуру своего модуля appData, у вас будут всевозможные ошибки в вашем модуле webService, пока вы не найдете все ссылки на старый формат и не переименуете их все.