Яндекс.Метрика

    Javascript

    Модульность в JavaScript

    Хочу поделиться своим опытом в написании модульного JS. Еще недавно обычно написаны скрипты выглядели аккуратно и упорядоченно, но сейчас в сравнение с модульным разбивкой по файлам это просто ужасно и очень неудобно. Постараюсь показать эту всю красоту на примере модуля «navigation» (AJAX навигация по сайту). Хочу также заметить, что это упрощеная, но не менее действенная система модулей которые уже когда-то предлагали на Хабре, чем и выгодна для применения даже в небольших проектах.

    Код состоит из трех основных частей:

    • Ядро
    • Песочница
    • Модули


    Чтобы уменьшить связь между объектами кода будем использовать асинхронную связь (для простоты будем пользоваться функциями jquery — bind и trigger).

    Пишем ядро вместе с песочницей ( '/scripts/core/core.js' )


    Кусок кода для будущего унаследование псевдоклассов:

    Function.prototype.extend = function (superClass) {
      var Inheritance = function () { };
      Inheritance.prototype = superClass.prototype;
      this.prototype = new Inheritance();
      this.prototype.constructor = this;
      this.superClass = superClass;
    }


    * This source code was highlighted with Source Code Highlighter.


    Далее основная функция js-приложения, где модули регистрируются:

    window.App = function ( data ) {
      switch (data.header) {
        case "attach":
          var library = {
            name: data.body.name,
            body: new data.body.module()
          };
          window.module_list[ library.name ] = library;
          library.body.main( );
          break;
        case "destroy":
          break;
    }

    * This source code was highlighted with Source Code Highlighter.


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

    Теперь по песочнице:

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

    window.Sandbox = {
      get: function ( data ) {
        var responce = [];
        var w = window;
        var d = document;
        if (data)
          for (var i = 0; i-data.length<=0; i++) {
            try { var d = data[i].split("|")[0]; } catch(e) { var d = data[i]; }
            switch (d) {
              case "window.location.hash": responce.push( w.location.hash ); break;
              case "$": responce.push( $( '#' + data[i].split('|')[1] ) ); break;
            }
          }
        return responce;
      },
      call: function( data ) {
        var library = {
          f_name: data.body.f_name,
          params: data.body.params,
          sandbox: function( e ) { data.body.sandbox( e ); }
        };

        switch ( library.f_name ) {
          case ("search"):
            $( '#search_page #search_t' ).attr( 'value', library.params.q );
            $( window ).trigger({
              type: "search_request",        
              query: library.params.q
            });
            break;
        }
      }
    }


    * This source code was highlighted with Source Code Highlighter.


    Нужно также добавить кроме обычного способа еще асинхронный через hook-и на определенные event `s.

    Ну и под конец инициализация:

    window.Module = {
      init: function() {},
      render: function() {}
    }

    !function () {

      window.module_list = [];
      
      $( window ).bind('core_request', window.App );
      
      $( document ).ready( init );
      
      function init() {
         /* ... */
      }
      
    } ();


    * This source code was highlighted with Source Code Highlighter.


    Модуль ( '/scripts/modules/naviagation/mod.js' )


    !function() {

    // INIT and configure module and additional parts
      var current_hash = '';
      var location = '';
      
    // RENDERING DOM of a page, when data is loaded (rebuilds the page)
      var render = function( result ) {
        if ( location===1 ) {
          $( '#content #bg1 #plus #page' ).animate({'opacity':'0'}, 300, function() {
            $( '#content' ).animate( {'height':'600px'}, 200 );
            $( '#content #bg1 #plus #page' ).html( result );
            $( '#content #bg1 #plus #page' ).animate({'opacity':'1'}, 300);
          });
        }
      }

    // Library construction
      var Lib = function() {
        this.main = function( data ) {
          var interval = setInterval( this.poll, 200);
        },
        this.poll = function() {
          var hash = window.Sandbox.get( ['window.location.hash|'] )[0];
          var aj = { url: '', param: '' };
          if (current_hash!==hash) {        // compare last received location.HASH with new one
            aj.param = '';
            aj.url = '';
            switch (hash) {
               case '#!/sign_in':
                aj.param = '';
                aj.url = '/parts/login.php';
                break;
            }

            if ( aj.url ) {                           // if we need to get new data:
              $.ajax({                            // load new page PARTS from server
                type: "GET",
                url: aj.url,
                data: aj.param,
                success: render
              });
            }
          current_hash = hash;
        }
      }
      Lib.extend( window.Module );

    // REGISTER this module in the CORE
      $( window ).trigger({
        type: "core_request",        
        header: "attach",
        body: {
          module: Lib,
          name: "navigation"
        }
      });

    } ();


    * This source code was highlighted with Source Code Highlighter.


    Так мы и завершили написание первого модуля. Просто и одновременно очень удобно!