Лучший способ организовать код jQuery / JavaScript (2013 г.) [закрыто]

104

Эта проблема

На этот ответ уже был дан ответ, но он старый и не актуален. У меня более 2000 строк кода в одном файле, и, как мы все знаем, это плохая практика, особенно когда я просматриваю код или добавляю новые функции. Я хочу лучше организовать свой код сейчас и в будущем.

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

Пример кода

$('#button1').on('click', function(e){
    // Determined action.
    update_html();
});

... // Around 75 more of this

function update_html(){ .... }

...

Еще пример кода

Вывод

Мне действительно нужно организовать этот код для лучшего использования, чтобы не повторяться и иметь возможность добавлять новые функции и обновлять старые. Я буду работать над этим сам. Некоторые селекторы могут состоять из 100 строк кода, другие - 1. Я немного посмотрел require.jsи обнаружил, что это отчасти повторяется, и на самом деле я написал больше кода, чем нужно. Я открыт для любого возможного решения, которое соответствует этим критериям, и ссылка на ресурсы / примеры всегда является плюсом.

Спасибо.

Кивилюс
источник
Если вы хотите добавить backbone.js и require.js, это потребует много работы.
jantimon
1
Какие задачи вы выполняете снова и снова, когда пишете это?
Майк Сэмюэл
4
Вы посетили codereview.stackexchange.com ?
Энтони
4
Изучите Angular! Это будущее.
Онур Йылдырым
2
Ваш код не должен быть по внешней ссылке, он должен быть здесь. Кроме того, @codereview - лучшее место для этих типов вопросов.
Джордж Стокер

Ответы:

98

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

Шаг 1. Разделите код

Разделение кода на несколько модульных блоков - очень хороший первый шаг. Соберите все, что работает «вместе», и поместите их в отдельный маленький корпус. пока не беспокойтесь о формате, оставьте его встроенным. Структура - более поздний момент.

Итак, предположим, у вас есть такая страница:

введите описание изображения здесь

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

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

Шаг 1а: Управление зависимостями

Используйте библиотеку, такую ​​как RequireJS или CommonJS, для реализации чего-то, что называется AMD . Асинхронная загрузка модуля позволяет вам явно указать, от чего зависит ваш код, что затем позволяет вам переложить вызов библиотеки в код. Вы можете буквально сказать: «Для этого нужен jQuery», и AMD загрузит его и выполнит ваш код, когда jQuery будет доступен .

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

Шаг 2: модульное построение

Видите каркас? У меня два рекламных блока. Скорее всего, у них будут общие слушатели событий.

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

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

ad_unit1.js

 $("#au1").click(function() { ... });

ad_unit2.js

 $("#au2").click(function() { ... });

У меня будет:

ad_unit.js:

 var AdUnit = function(elem) {
     this.element = elem || new jQuery();
 }
 AdUnit.prototype.bindEvents = function() {
     ... Events go here
 }

page.js:

 var AUs = new AdUnit($("#au1,#au2"));
 AUs.bindEvents();

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

Шаг 3. Выберите фреймворк!

Если вы хотите еще больше разделить на модули и сократить количество повторений, существует множество замечательных фреймворков, реализующих подходы MVC (Модель - Представление - Контроллер). Мне больше всего нравится Backbone / Spine, но есть также Angular, Yii, ... Список можно продолжить.

Модель представляет данные.

View представляет свою наценку и все события , связанные с ним

Контроллер представляет бизнес - логику - другими словами, контроллер сообщает страницу , какие представления для нагрузки и какие модели использования.

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

Есть много других вещей, которые вы можете сделать, это просто рекомендации и идеи.

Изменения, специфичные для кода

Вот некоторые конкретные улучшения вашего кода:

 $('.new_layer').click(function(){

    dialog("Create new layer","Enter your layer name","_input", {

            'OK' : function(){

                    var reply = $('.dialog_input').val();

                    if( reply != null && reply != "" ){

                            var name = "ln_"+reply.split(' ').join('_');
                            var parent = "";

                            if(selected_folder != "" ){
                            parent = selected_folder+" .content";
                            }

                            $R.find(".layer").clone()
                            .addClass(name).html(reply)
                            .appendTo("#layer_groups "+parent);

                            $R.find(".layers_group").clone()
                            .addClass(name).appendTo('#canvas '+selected_folder);

            }

        }

    });
 });

Лучше записать это так:

$("body").on("click",".new_layer", function() {
    dialog("Create new layer", "Enter your layer name", "_input", {
         OK: function() {
             // There must be a way to get the input from here using this, if it is a standard library. If you wrote your own, make the value retrievable using something other than a class selector (horrible performance + scoping +multiple instance issues)

             // This is where the view comes into play. Instead of cloning, bind the rendering into a JS prototype, and instantiate it. It means that you only have to modify stuff in one place, you don't risk cloning events with it, and you can test your Layer stand-alone
             var newLayer = new Layer();
             newLayer
               .setName(name)
               .bindToGroup(parent);
          }
     });
});

Ранее в вашем коде:

window.Layer = function() {
    this.instance = $("<div>");
    // Markup generated here
};
window.Layer.prototype = {
   setName: function(newName) {
   },
   bindToGroup: function(parentNode) {
   }
}

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

Еще один:

// Обертка набора правил для действий

var PageElements = function(ruleSet) {
ruleSet = ruleSet || [];
this.rules = [];
for (var i = 0; i < ruleSet.length; i++) {
    if (ruleSet[i].target && ruleSet[i].action) {
        this.rules.push(ruleSet[i]);
    }
}
}
PageElements.prototype.run = function(elem) {
for (var i = 0; i < this.rules.length; i++) {
    this.rules[i].action.apply(elem.find(this.rules.target));
}
}

var GlobalRules = new PageElements([
{
    "target": ".draggable",
    "action": function() { this.draggable({
        cancel: "div#scrolling, .content",
        containment: "document"
        });
    }
},
{
    "target" :".resizable",
    "action": function() {
        this.resizable({
            handles: "all",
            zIndex: 0,
            containment: "document"
        });
    }
}

]);

GlobalRules.run($("body"));

// If you need to add elements later on, you can just call GlobalRules.run(yourNewElement);

Это очень эффективный способ регистрации правил, если у вас есть нестандартные события или события создания. Это также является серьезным ударом в сочетании с системой уведомлений pub / sub и когда привязано к событию, которое вы запускаете при создании элементов. Модульная привязка событий Fire'n'forget!

Себастьен Рено
источник
2
@ Джессика: почему онлайн-инструмент должен быть другим? Подход остается тем же: разделить / модулировать, способствовать слабой связи между компонентами с помощью фреймворка (в наши дни все они идут с делегированием событий), разделить код на части. Что там не относится к вашему инструменту? То, что кнопок у вас много?
Себастьен Рено,
2
@ Джессика: Обновлено. Я упростил и оптимизировал создание слоев, используя концепцию, аналогичную концепции View. Так. Как это не относится к вашему коду?
Себастьен Рено,
10
@Jessica: Разделение на файлы без оптимизации похоже на покупку дополнительных ящиков для хранения мусора. Однажды вам нужно убраться, и вам будет легче убрать до того, как ящики заполнятся. Почему бы не сделать и то, и другое? Прямо сейчас, выглядит , как вы будете хотеть layers.js, sidebar.js, global_events.js, resources.js, files.js, dialog.jsесли вы только собираетесь разделить свой код на. Используйте, gruntчтобы Google Closure Compilerсобрать их в один, а также скомпилировать и свернуть.
Себастьян Рено
3
При использовании require.js вы также должны внимательно изучить оптимизатор r.js, это действительно то, что делает использование require.js полезным. Он объединит и оптимизирует все ваши файлы: requirejs.org/docs/optimization.html
Виллем Д'Хазелер
2
@ SébastienRenauld, ваш ответ и комментарии по-прежнему очень ценятся другими пользователями. Если это поможет вам почувствовать себя лучше;)
Adrien Be
13

Вот простой способ разделить текущую кодовую базу на несколько файлов с помощью require.js. Я покажу вам, как разбить код на два файла. После этого добавить больше файлов будет просто.

Шаг 1) В верхней части кода создайте объект приложения (или любое другое имя, которое вы предпочитаете, например MyGame):

var App = {}

Шаг 2) Преобразуйте все переменные и функции верхнего уровня в объект App.

Вместо того:

var selected_layer = "";

Вы хотите:

App.selected_layer = "";

Вместо того:

function getModified(){
...
}

Вы хотите:

App.getModified = function() {

}

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

Шаг 3) Преобразуйте все ссылки на глобальные переменные и функции, чтобы они проходили через приложение.

Измените такие вещи, как:

selected_layer = "."+classes[1];

кому:

App.selected_layer = "."+classes[1];

и:

getModified()

кому:

App.GetModified()

Шаг 4) Протестируйте свой код на этом этапе - все должно работать. Возможно, сначала вы получите несколько ошибок, потому что что-то упустили, поэтому исправьте их, прежде чем двигаться дальше.

Шаг 5) Настройте requirejs. Я предполагаю, что у вас есть веб-страница, обслуживаемая веб-сервером, код которого находится в:

www/page.html

и jquery в

www/js/jquery.js

Если эти пути не совсем такие , как это, приведенное ниже не будет работать, и вам придется изменить пути.

Загрузите requirejs и поместите require.js в свой www/jsкаталог.

в вашем page.html, удалите все теги сценария и вставьте тег сценария, например:

<script data-main="js/main" src="js/require.js"></script>

создать www/js/main.jsс содержанием:

require.config({
 "shim": {
   'jquery': { exports: '$' }
 }
})

require(['jquery', 'app']);

затем поместите весь код, который вы только что исправили на шагах 1-3 (единственная глобальная переменная которого должна быть App) в:

www/js/app.js

В самом верху этого файла поместите:

require(['jquery'], function($) {

Внизу поставьте:

})

Затем загрузите page.html в свой браузер. Ваше приложение должно работать!

Шаг 6) Создайте еще один файл

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

Вытащите код из www/js/app.jsссылок на $ и App.

например

$('a').click(function() { App.foo() }

Положи это в www/js/foo.js

В самом верху этого файла поместите:

require(['jquery', 'app'], function($, App) {

Внизу поставьте:

})

Затем измените последнюю строку www / js / main.js на:

require(['jquery', 'app', 'foo']);

Это оно! Делайте это каждый раз, когда хотите поместить код в отдельный файл!

Лин Хедли
источник
У этого есть несколько проблем - очевидная из них заключается в том, что вы фрагментируете все свои файлы в конце и вынуждаете 400 байтов потраченных впустую данных каждому пользователю на каждый скрипт на загрузку страницы , не используя r.jsпрепроцессор. Кроме того, вы на самом деле не рассмотрели проблему OP - просто предоставили общее require.jsруководство.
Себастьян Рено
7
А? Мой ответ конкретно на этот вопрос. Очевидно, что следующим шагом будет r.js, но проблема здесь в организации, а не в оптимизации.
Лин Хедли
Мне нравится этот ответ, я никогда не использовал require.js, поэтому мне нужно будет посмотреть, смогу ли я его использовать и получить от него какую-то пользу. Я активно использую шаблон модуля, но, возможно, это позволит мне абстрагироваться от некоторых вещей, а затем потребовать их включения.
Тони
1
@ SébastienRenauld: этот ответ касается не только require.js. В основном это говорит о наличии пространства имен для кода, который вы создаете. Я думаю, вы должны оценить хорошие части и отредактировать, если обнаружите какие-либо проблемы с этим. :)
mithunsatheesh 04
10

Что касается вашего вопроса и комментариев, я предполагаю, что вы не хотите переносить свой код на фреймворк, такой как Backbone, или использовать библиотеку загрузчика, такую ​​как Require. Вам просто нужен лучший способ организовать код, который у вас уже есть, самым простым из возможных способов.

Я понимаю, что прокручивать более 2000 строк кода, чтобы найти раздел, над которым вы хотите работать, раздражает. Решение состоит в том, чтобы разделить ваш код на разные файлы, по одному для каждой функции. Например sidebar.js, canvas.jsи т. Д. Затем вы можете объединить их вместе для производства с помощью Grunt, вместе с Usemin вы можете получить что-то вроде этого:

В вашем html:

<!-- build:js scripts/app.js -->
<script src="scripts/sidebar.js"></script>
<script src="scripts/canvas.js"></script>
<!-- endbuild -->

В вашем Gruntfile:

useminPrepare: {
  html: 'app/index.html',
  options: {
    dest: 'dist'
  }
},
usemin: {
  html: ['dist/{,*/}*.html'],
  css: ['dist/styles/{,*/}*.css'],
  options: {
    dirs: ['dist']
  }
}

Если вы хотите использовать Yeoman, он предоставит вам шаблонный код для всего этого.

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

sidebar.js

var Sidebar = (function(){
// functions and vars here are private
var init = function(){
  $("#sidebar #sortable").sortable({
            forceHelperSize: true,
            forcePlaceholderSize: true,
            revert: true,
            revert: 150,
            placeholder: "highlight panel",
            axis: "y",
            tolerance: "pointer",
            cancel: ".content"
       }).disableSelection();
  } 
  return {
   // here your can put your "public" functions
   init : init
  }
})();

Затем вы можете загрузить этот фрагмент кода следующим образом:

$(document).ready(function(){
   Sidebar.init();
   ...

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

Хесус Каррера
источник
1
Вы можете серьезно пересмотреть этот предпоследний фрагмент кода, который ничем не лучше, чем встроенный код: ваш модуль требует #sidebar #sortable. Вы также можете сэкономить память, просто вставив код и сохранив два IETF.
Себастьен Рено
Дело в том, что вы можете использовать любой код, который вам нужен. Я просто использую пример из исходного кода
Хесус Каррера
Я согласен с Иисусом, это всего лишь пример, OP может легко добавить «объект» параметров, который позволил бы им указать селектор элемента вместо его жесткого кодирования, но это был всего лишь быстрый пример. Я хотел бы сказать, что мне нравится шаблон модуля, это основной шаблон, который я использую, но даже несмотря на это, я все еще пытаюсь лучше организовать свой код. Обычно я использую C #, поэтому именование и создание функций кажется таким универсальным. Я стараюсь сохранить «шаблон», например, подчеркивание является локальным и частным, переменные - это просто «объекты», а затем я ссылаюсь на функцию в моем возвращении, которая является общедоступной.
Тони
Однако я все еще сталкиваюсь с проблемами с этим подходом и хочу найти лучший способ сделать это. Но это работает намного лучше, чем просто объявление моих переменных и функций в глобальном пространстве, чтобы вызвать конфликты с другими js .... lol
Тони
6

Используйте javascript MVC Framework для стандартной организации кода javascript.

Лучшие доступные фреймворки JavaScript MVC:

Выбор фреймворка JavaScript MVC требовал учета множества факторов. Прочтите следующую сравнительную статью, которая поможет вам выбрать лучший фреймворк на основе факторов, важных для вашего проекта: http://sporto.github.io/blog/2013/04/12/comparison-angular-backbone-can-ember/

Вы также можете использовать RequireJS с фреймворком для поддержки загрузки Asynchrounous js файлов и модулей.
Посмотрите ниже, чтобы начать загрузку модуля JS:
http://www.sitepoint.com/understanding-requirejs-for-effective-javascript-module-loading/

Рохит Портной
источник
4

Отнесите свой код к категории. Этот метод мне очень помогает и работает с любым js-фреймворком:

(function(){//HEADER: menu
    //your code for your header
})();
(function(){//HEADER: location bar
    //your code for your location
})();
(function(){//FOOTER
    //your code for your footer
})();
(function(){//PANEL: interactive links. e.g:
    var crr = null;
    $('::section.panel a').addEvent('click', function(E){
        if ( crr) {
            crr.hide();
        }
        crr = this.show();
    });
})();

В предпочитаемом вами редакторе (лучше всего это Komodo Edit) вы можете свернуть, свернув все записи, и вы увидите только заголовки:

(function(){//HEADER: menu_____________________________________
(function(){//HEADER: location bar_____________________________
(function(){//FOOTER___________________________________________
(function(){//PANEL: interactive links. e.g:___________________

источник
2
+1 за стандартное решение JS, которое не полагается на библиотеки.
Hobberwickey
-1 по нескольким причинам. Ваш эквивалент кода точно такой же, как OP ... + один IETF на "раздел". Кроме того, вы используете слишком широкие селекторы, не позволяя разработчикам модулей отменять создание / удаление тех или расширять поведение. Наконец, IETF не бесплатны.
Себастьян Рено
@hobberwickey: Не знаю, как вы, но я бы предпочел полагаться на то, что принадлежит сообществу и где ошибки будут обнаружены быстро, если я смогу. Особенно, если поступление иначе обрекает меня изобретать колесо.
Себастьен Рено
2
Все, что это делает, - это организация кода в отдельные разделы. В прошлый раз я проверил, что это A: хорошая практика и B: не то, для чего вам действительно нужна библиотека, поддерживаемая сообществом. Не все проекты вписываются в Backbone, Angular и т. Д., И разбиение кода на модули путем обертывания его функциями является хорошим общим решением.
Hobberwickey
Вы можете в любое время полагаться на любую любимую библиотеку, чтобы использовать этот подход. Но приведенное выше решение работает с чистым javascript, пользовательскими библиотеками или любым известным фреймворком js
3

Я бы посоветовал:

  1. шаблон издатель / подписчик для управления событиями.
  2. ориентация объекта
  3. пространство имен

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

Фазле раввин
источник
Можете ли вы расширить это с помощью примеров / ресурсов?
Кивилиус
1
Что вы имеете в виду под «объектной ориентацией»? Почти все в JS - это объект. И в JS нет классов.
Bergi
2

Я предлагаю вам использовать что-то вроде Backbone. Backbone - это RESTFUL-поддерживаемая библиотека javascript. Ik делает ваш код более чистым и читаемым, а также эффективен при использовании вместе с requirejs.

http://backbonejs.org/

http://requirejs.org/

Backbone - это не настоящая библиотека. Он предназначен для структурирования вашего кода javascript. Он может включать другие библиотеки, такие как jquery, jquery-ui, google-maps и т. Д. Backbone, на мой взгляд, является наиболее близким подходом javascript к объектно-ориентированным структурам и структурам контроллера представления модели.

Также относительно вашего рабочего процесса. Если вы создаете свои приложения на PHP, используйте библиотеку Laravel. Он будет безупречно работать с Backbone при использовании с принципом RESTfull. Ниже ссылка на Laravel Framework и руководство по созданию RESTfull API:

http://maxoffsky.com/code-blog/building-restful-api-in-laravel-start-here/

http://laravel.com/

Ниже приведено руководство от nettuts. Nettuts имеет множество руководств в высоком качестве:

http://net.tutsplus.com/tutorials/javascript-ajax/understanding-backbone-js-and-the-server/

Крис Виссер
источник
0

Может быть, вам пора начать реализовывать целый рабочий процесс разработки, используя такие инструменты, как yeoman http://yeoman.io/ . Это поможет контролировать все ваши зависимости, процесс сборки и, если хотите, автоматическое тестирование. Для начала нужно много работать, но после его внедрения будущие изменения будут намного проще.

Дробсон
источник