Лучший способ организовать код 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/ . Это поможет контролировать все ваши зависимости, процесс сборки и, если хотите, автоматическое тестирование. Для начала нужно много работать, но после его внедрения будущие изменения будут намного проще.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.