Best way to organize jQuery/JavaScript code (2013)

2019-03-07 09:18发布

The Problem

This answer has been answered before but are old and not up to date. I have over 2000 lines of code in a single file, and as we all know this is bad practice, especially when i'm looking through code or adding new features. I want to better organize my code, for now and for the future.

I should mention that I'm building a tool (not a simple website) with lots of buttons, UI elements, drag, drops, action listeners/handlers and function in the global scope where several listeners may use the same function.

Example code

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

... // Around 75 more of this

function update_html(){ .... }

...

More example code

Conclusion

I really need to organize this code for best use and not to repeat myself and be able to add new features and update old ones. I will be working on this by myself. Some selectors can be 100 lines of code others are 1. I have looked a bit at require.js and found it kinda repetitive, and actually writing more code than needed . I'm open to any possible solution that fit this criteria and link to resource / examples are always a plus.

Thanks.

8条回答
Emotional °昔
2楼-- · 2019-03-07 09:45

Categorize your code. This method is helping me a lot and does work with any js framework:

(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();
    });
})();

In your preferred editor (the best is Komodo Edit) you may fold in by collapsing all entries and you will see only the titles:

(function(){//HEADER: menu_____________________________________
(function(){//HEADER: location bar_____________________________
(function(){//FOOTER___________________________________________
(function(){//PANEL: interactive links. e.g:___________________
查看更多
三岁会撩人
3楼-- · 2019-03-07 09:47

For your question and comments I'll assume you are not willing to port your code to a framework like Backbone, or use a loader library like Require. You just want a better way to orgainze the code that you already have, in the simplest way possible.

I understand it is annoying to scroll through 2000+ lines of code to find the section that you want to work on. The solution is to split your code in different files, one for each functionality. For example sidebar.js, canvas.js etc. Then you can join them together for production using Grunt, together with Usemin you can have something like this:

In your html:

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

In your Gruntfile:

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

If you want to use Yeoman it will give you a boilerplate code for all this.

Then for each file itself, you need to make sure you follow best practices and that all the code and variables are all in that file, and don't depend on other files. This doesn't mean you can't call functions of one file from other, the point is to have variables and functions encapsulated. Something similar to namespacing. I'll assume you don't want to port all your code to be Object Oriented, but if you don't mind refactoring a bit, I'd recommend to add something equivalent with what is called a Module pattern. It looks something like this:

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
  }
})();

Then you can load this bit of code like this:

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

This will allow you to have a much more maintainable code without having to rewrite your code too much.

查看更多
我只想做你的唯一
4楼-- · 2019-03-07 09:53

Use javascript MVC Framework in order to organize the javascript code in a standard way.

Best JavaScript MVC frameworks available are:

Selecting a JavaScript MVC framework required so many factors to consider. Read the following comparison article that will help you to select best framework based on the factors important for your project: http://sporto.github.io/blog/2013/04/12/comparison-angular-backbone-can-ember/

You can also use RequireJS with the framework to support Asynchrounous js file & module loading.
Look the below to get started on JS Module loading:
http://www.sitepoint.com/understanding-requirejs-for-effective-javascript-module-loading/

查看更多
Animai°情兽
5楼-- · 2019-03-07 09:54

I would suggest:

  1. publisher/subscriber pattern for event management.
  2. object orientation
  3. namespacing

In your case Jessica, divide the interface into pages or screens. Pages or screens can be objects and extended from some parent classes. Manage the interactions among pages with a PageManager class.

查看更多
够拽才男人
6楼-- · 2019-03-07 09:58

I'll go over some simple things that may, or may not, help you. Some might be obvious, some might be extremely arcane.

Step 1: Compartmentalize your code

Separating your code into multiple, modular units is a very good first step. Round up what works "together" and put them in their own little encased unit. don't worry about the format for now, keep it inline. The structure is a later point.

So, suppose you have a page like this:

enter image description here

It would make sense to compartmentalize so that all the header-related event handlers/binders are in there, for ease of maintenance (and not having to sift through 1000 lines).

You can then use a tool such as Grunt to re-build your JS back to a single unit.

Step 1a: Dependency management

Use a library such as RequireJS or CommonJS to implement something called AMD. Asynchronous Module Loading allows you to explicitely state what your code depends on, which then allows you to offload the library-calling to the code. You can just literally say "This needs jQuery" and the AMD will load it, and execute your code when jQuery is available.

This also has a hidden gem: the library loading will be done the second the DOM is ready, not before. This no longer halts load-up of your page!

Step 2: Modularize

See the wireframe? I have two ad units. They'll most likely have shared event listeners.

Your task in this step is to identify the points of repetition in your code and to try to synthesise all this into modules. Modules, right now, will encompass everything. We'll split stuff as we go along.

The whole idea of this step is to go from step 1 and delete all the copy-pastas, to replace them with units that are loosely coupled. So, instead of having:

ad_unit1.js

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

ad_unit2.js

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

I will have:

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();

Which allows you to compartmentalize between your events and your markup in addition to getting rid of repetition. This is a pretty decent step and we'll extend this further later on.

Step 3: Pick a framework!

If you'd like to modularize and reduce repetitions even further, there are a bunch of awesome frameworks around that implement MVC (Model - View - Controller) approaches. My favourite is Backbone/Spine, however, there's also Angular, Yii, ... The list goes on.

A Model represents your data.

A View represents your mark-up and all the events associated to it

A Controller represents your business logic - in other words, the controller tells the page what views to load and what models to use.

This will be a significant learning step, but the prize is worth it: it favours clean, modular code over spaghetti.

There are plenty of other things you can do, those are just guidelines and ideas.

Code-specific changes

Here are some specific improvements to your code:

 $('.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);

            }

        }

    });
 });

This is better written as:

$("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);
          }
     });
});

Earlier in your code:

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

Suddenly, you have a way to create a standard layer from anywhere in your code without copy pasting. You're doing this in five different places. I've just saved you five copy-pastes.

One more:

// Ruleset wrapper for actions

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);

This is a very potent way to register rules if you have events that are not standard, or creation events. This is also seriously kick-ass when combined with a pub/sub notification system and when bound to an event you fire whenever you create elements. Fire'n'forget modular event binding!

查看更多
可以哭但决不认输i
7楼-- · 2019-03-07 10:07

Maybe its time for you start implementing a whole development workflow using such tools as yeoman http://yeoman.io/. This will help control all your dependencies, build process and if you wanted, automated testing. Its a lot of work to start with but once implemented will make future changes a lot easier.

查看更多
登录 后发表回答