Найкращий спосіб організації 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 і requ.js, це буде багато роботи.
jantimon

1
Які завдання ви ставите перед собою знову і знову, коли пишете це?
Майк Самуель

4
Ви відвідували codereview.stackexchange.com ?
Антоній

4
Дізнайтеся Angular! Це майбутнє.
Onur Yıldırım

2
Ваш код не повинен бути на зовнішньому посиланні, він повинен бути тут. Також @codereview є кращим місцем для цих типів питань.
Джордж Стокер

Відповіді:


98

Я перегляну кілька простих речей, які можуть допомогти вам, а можуть і не допомогти. Деякі можуть бути очевидними, деякі - надзвичайно задушливими.

Крок 1: Продемонструйте свій код

Розділення коду на кілька модульних одиниць - це дуже хороший перший крок. Закруглете те, що працює «разом», і покладіть їх у свій маленький укладений блок. не хвилюйтесь про формат поки що, тримайте його в Inline. Структура є пізнішим моментом.

Отже, припустимо, у вас є така сторінка:

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

Було б сенс розділити так, щоб усі обробники подій, що стосуються заголовка, були там, для зручності обслуговування (і не потрібно просіювати 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 (Model - View - Controller). Моя улюблена - хребет / хребет, однак, є також 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);

Це дуже потужний спосіб реєстрації правил, якщо у вас є події, які не є стандартними, або події створення. Це також є серйозним ударом у поєднанні із системою сповіщень про паб / підкоманду та у зв’язку із подією, яку ви запускаєте під час створення елементів. Забудьте про модульну прив'язку подій!


2
@Jessica: чому веб-інструмент повинен відрізнятися? Підхід залишається тим самим: розділіть / модулюйте, просувайте зв'язане з'єднання між компонентами за допомогою фреймворку (всі вони приходять з делегуванням подій в ці дні), розділіть свій код на частини. Що там не стосується вашого інструменту? Те, що у вас багато кнопок?
Себастьян Ренольд

2
@ Джессіка: оновлено. Я спростив і впорядкував створення шарів за допомогою концепції, подібної до View. Так. Як це не стосується вашого коду?
Sébastien Renauld

10
@Jessica: Розбиття файлів без оптимізації - це як придбання більшої кількості ящиків для зберігання сміття. Одного разу вам доведеться очистити, і простіше очистити, перш ніж ящики заповнити. Чому б не зробити обох? Прямо зараз, виглядає , як ви будете хотіти layers.js, sidebar.js, global_events.js, resources.js, files.js, dialog.jsякщо ви тільки збираєтесь розділити свій код на. Використовуйте gruntдля перебудови їх в одне ціле і Google Closure Compilerдля складання та мінімізації.
Sébastien Renauld

3
Під час використання requ.js ви також повинні заглянути в оптимізатор r.js, і це дійсно те, що вимагає. Він поєднає та оптимізує всі ваші файли: Requjs.org/docs/optimization.html
Willem D'Haeseleer

2
@ SébastienRenauld Ваша відповідь та коментарі досі високо оцінені іншими користувачами. Якщо це може змусити вас почувати себе краще;)
Адрієн Будь

13

Ось простий спосіб розділити поточну кодову базу на кілька файлів, використовуючи Requijs. Я покажу вам, як розділити ваш код на два файли. Після цього додати більше файлів буде просто.

Крок 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) Налаштування Requjs. Я припускаю, що у вас є веб-сторінка, яка обслуговується з веб-сервера, чий код знаходиться в:

www/page.html

і jquery в

www/js/jquery.js

Якщо ці шляхи не зовсім такі , як описано нижче, нижче не вийде, і вам доведеться змінювати шляхи.

Завантажте Requjs і покладіть Require.S у свій 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 (єдиною глобальною змінною має бути додаток) у:

www/js/app.js

У верхній частині цього файлу поставте:

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

У нижній частині покласти:

})

Потім завантажте сторінку.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попереднього процесу. Крім того, ви насправді не вирішили питання ОП - просто надали загальну require.jsпрактику.
Sébastien Renauld

7
Так? Моя відповідь специфічна для цього питання. І r.js, очевидно, наступний крок, але питання тут - організація, а не оптимізація.
Лін Хедлі

Мені подобається ця відповідь, я ніколи не використовував requ.js, тому мені доведеться побачити, чи можу я ним скористатися і отримати від цього якусь користь. Я дуже використовую шаблон модуля, але, можливо, це дозволить мені абстрагувати деякі речі, а потім вимагати їх.
Тоні,

1
@ SébastienRenauld: ця відповідь стосується не лише вимоги.js. В основному це говорить про те, щоб мати простір імен для коду, який ви будуєте. Я думаю, що ви повинні оцінити хороші частини та внести зміни, коли ви знайдете будь-які проблеми з цим. :)
mithunsatheesh

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.
Себастьян Ренольд

Справа в тому, що ви можете використовувати будь-який код, який вам потрібен. Я просто використовую приклад з оригінального коду
Jesús Carrera

Я погоджуюся з Ісусом, це лише приклад, ОП може легко додати параметр "об'єкт", який би дозволив їм вказати селектор елемента замість жорсткого кодування, але це був лише швидкий приклад. Я хотів би сказати, що мені подобається шаблон модуля, це основний зразок, який я використовую, але навіть маючи на увазі, я все ще намагаюся краще організувати свій код. Я зазвичай використовую C #, тому функція іменування та створення виглядає настільки загальною. Я намагаюся зберігати "візерунок", як підкреслення - локальні та приватні, змінні - просто "об'єкти", і тоді я посилаюсь на функцію у відповідь, яка є загальнодоступною.
Тоні

Однак я все ще знаходжу проблеми з таким підходом і прагну покращити це. Але це працює набагато краще, ніж просто оголосити мої змінні та функції в глобальному просторі, щоб викликати конфлікти з іншими js .... lol
Тоні

6

Використовуйте java MVC Framework для того, щоб організувати код javascript у стандартний спосіб.

Найкращі доступні рамки JavaScript MVC:

Вибір рамки MVC JavaScript вимагає врахування багатьох факторів. Прочитайте наступну статтю порівняння, яка допоможе вам вибрати найкращу основу на основі факторів, важливих для вашого проекту: http://sporto.github.io/blog/2013/04/12/comppare-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) ви можете скласти, згорнувши всі записи, і ви побачите лише заголовки:

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

2
+1 для стандартного рішення JS, яке не покладається на бібліотеки.
hobberwickey

-1 з кількох причин. Ваш еквівалент коду точно такий же, як ОП ... + один IETF на "секцію". Крім того, ви використовуєте занадто широкі селектори, не дозволяючи розробникам модулів перекривати створення / видалення цих або розширювати поведінку. Нарешті, IETF не є безкоштовними.
Sébastien Renauld

@hobberwickey: Не знаю про вас, але я скоріше покладусь на те, що керується громадою і де помилки будуть швидко знайдені, якщо я зможу. Особливо, якщо діяти в іншому випадку, засуджує мене знову винайти колесо.
Sébastien Renauld

2
Все це робиться - це впорядкування коду в дискретні розділи. Минулого разу я перевірив, що це: «хороша практика» та «Б» - не те, що вам справді потрібна бібліотека, що підтримується громадою. Не всі проекти вписуються в магістральну, кутову тощо, і модуляризуючий код, перетворюючи його на функції, є хорошим загальним рішенням.
hobberwickey

Цей підхід можна в будь-який час покластись на будь-яку улюблену бібліотеку. Але вищезазначене рішення працює з чистим JavaScript, користувацькими бібліотеками або будь-яким відомим фреймворком js

3

Я б запропонував:

  1. шаблон видавця / підписника для управління подіями.
  2. орієнтація об'єкта
  3. простір імен

У вашому випадку Джессіка розділіть інтерфейс на сторінки чи екрани. Сторінки або екрани можуть бути об'єктами та розширені з деяких батьківських класів. Керуйте взаємодією сторінок із класом PageManager.


Чи можете ви розширити це за допомогою прикладів / ресурсів?
Кивилій

1
Що ви маєте на увазі під «орієнтацією на об’єкт»? Майже все в JS є об’єктом. І в JS класів немає.
Бергі

2

Я пропоную вам скористатися чимось на кшталт Backbone. Магістраль - це БЕЗКОШТОВНА підтримка бібліотеки javascript. Ik робить ваш код більш чистим і читабельним і потужним при використанні разом із Requjs.

http://backbonejs.org/

http://requirejs.org/

Хребна основа - не справжня бібліотека. Він призначений надати структуру вашому коду JavaScript. Він може включати інші бібліотеки, такі як jquery, jquery-ui, google-maps тощо. На мою думку, Backbone - це найближчий підхід javascript до об'єктно-орієнтованих та модельних контролер структур.

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

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

http://laravel.com/

Нижче - підручник із сітки. У 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.