ChatGPT解决这个技术问题 Extra ChatGPT

Best way to organize jQuery/JavaScript code (2013) [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center. Closed 9 years ago.

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.

If you want to add backbone.js and require.js it will be a lot of work.
What tasks do you find yourself doing over and over when writing this?
Have you visited codereview.stackexchange.com?
Learn Angular! It's the future.
Your code should not be at an external link, it should be here. Also, @codereview is a better place for tehse types of questions.

S
Sébastien Renauld

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:

https://i.stack.imgur.com/YaaJ6.png

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!


@Jessica: why should an online tool be any different? The approach is still the same: compartmentalize/modularize, promote loose coupling between components using a framework (they all come with event delegation these days), split your code apart. What is there that does not apply to your tool there? The fact that you have plenty of buttons?
@Jessica: Updated. I've simplified and streamlined the creation of Layers using a concept similar to a View. So. How does this not apply to your code?
@Jessica: Splitting into files without optimizing is like buying more drawers to store junk. One day, you've got to clear out, and it's easier to clear out before the drawers fill over. Why not do both? Right now, looks like you'll want a layers.js, sidebar.js, global_events.js, resources.js, files.js, dialog.js if you're just going to split your code up. Use grunt to rebuild them into one and Google Closure Compiler to compile and minimize.
When using require.js you must really look into the r.js optimizer as well, this is really what makes require.js worth using. It will combine and optimize all your files: requirejs.org/docs/optimization.html
@SébastienRenauld your answer & comments are still very appreciated by other users. If that can make you feel better ;)
L
Lyn Headley

Here is a simple way to split your current codebase into multiple files, using require.js. I will show you how to split your code into two files. Adding more files will be straightforward after that.

Step 1) At the top of your code, create an App object (or whatever name you prefer, like MyGame):

var App = {}

Step 2) Convert all of your top-level variables and functions to belong to the App object.

Instead of:

var selected_layer = "";

You want:

App.selected_layer = "";

Instead of:

function getModified(){
...
}

You want:

App.getModified = function() {

}

Note that at this point your code will not work until you finish the next step.

Step 3) Convert all global variable and function references to go through App.

Change stuff like:

selected_layer = "."+classes[1];

to:

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

and:

getModified()

to:

App.GetModified()

Step 4) Test Your code at this stage -- it should all work. You will probably get a few errors at first because you missed something, so fix those before moving on.

Step 5) Set up requirejs. I assume you have a web page, served from a web server, whose code is in:

www/page.html

and jquery in

www/js/jquery.js

If these paths are not exactly like this the below will not work and you'll have to modify the paths.

Download requirejs and put require.js in your www/js directory.

in your page.html, delete all script tags and insert a script tag like:

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

create www/js/main.js with content:

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

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

then put all the code you just fixed up in Steps 1-3 (whose only global variable should be App) in:

www/js/app.js

At the very top of that file, put:

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

At the bottom put:

})

Then load page.html in your browser. Your app should work!

Step 6) Create another file

Here is where your work pays off, you can do this over and over.

Pull out some code from www/js/app.js that references $ and App.

e.g.

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

Put it in www/js/foo.js

At the very top of that file, put:

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

At the bottom put:

})

Then change the last line of www/js/main.js to:

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

That's it! Do this every time you want to put code in its own file!


This has multiple problems - the obvious one being that you're fragmenting all your files at the end and forcing 400 bytes of wasted data to every user per script per page load by not using the r.js preprocessor. Furthermore, you haven't actually addressed the OP's issue - merely provided a generic require.js howto.
Huh? My answer is specific to this question. And r.js is obviously the next step but the issue here is organization, not optimization.
I like this answer, I have never used require.js so I will have to see if I can use it and get any benefit from it. I use the module pattern heavily but perhaps this will allow me to abstract some things out and then require them in.
@SébastienRenauld : this answer is not merely about require.js. It speaks mostly about having a namespace for the code you are building. I think you should appreciate the good parts and make an edit you find any issues with it. :)
j
joshweir

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.


You may want to seriously reconsider that second-to-last snippet, which is no better than having code written inline: your module requires #sidebar #sortable. You may as well save yourself memory by just inlining the code and saving the two IETFs.
The point there is that you can use whatever code you need. I'm just using an example from the original code
I agree with Jesus this is just an example, the OP can easily add an options "object" that would allow them to specify the selector of the element instead of hard coding it but this was just a quick example. I would like to say that I love the module pattern it is the primary pattern I use but even with that said I am still trying to better organize my code. I use C# normally so the function naming and creation feels so generic. I try keeping a "pattern" like underscores are local and private, variables are just "objects" and then I reference the function in my return which is public.
I however still find challenges with this approach and desire to have a better way of doing this. But it works a lot better than just declaring my variables and functions in the global space to cause conflicts with other js.... lol
R
Rohit Tailor

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

Best JavaScript MVC frameworks available are:

Backbone

Angular

CanJS

Ember

ReactJS

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/


佚名

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:___________________

+1 for a standard JS solution that doesn't rely on libraries.
-1 for multiple reasons. Your code equivalent is exactly the same as the OP's... + one IETF per "section". Furthermore, you are using overly broad selectors without allowing module developers to override the creation/removal of those or to extend behaviour. Finally, IETFs are not free.
@hobberwickey: Don't know about you, but I'd rather rely on something that is community-driven and where bugs will be found quickly if I can. Especially if doing otherwise condemns me to re-invent the wheel.
All this is doing is organizing code into discrete sections. Last time I checked that was A: good practice, and B: not something you really need a community supported library for. Not all projects fit into Backbone, Angular, etc. and modularizing code by wrapping it in functions is a good general solution.
It is possible anytime you want rely on any favorite library to use this approach. But the above solution works with pure javascript, custom libraries or any famous js framework
F
Fazle Rabbi

I would suggest:

publisher/subscriber pattern for event management. object orientation 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.


Can you expand on this with examples / resources?
What do you mean by "object orientation"? Nearly everything in JS is an object. And there are no classes in JS.
C
Chris Visser

I suggest that you use something like Backbone. Backbone is a RESTFUL supported javascript library. Ik makes your code cleaner and more readable and is powerful when used together with requirejs.

http://backbonejs.org/

http://requirejs.org/

Backbone isn't a real library. It is meant to give structure to your javascript code. It is able to include other libraries like jquery, jquery-ui, google-maps etc. Backbone is in my opinion the closest javascript approach to Object Oriented and Model View Controller structures.

Also regarding your workflow.. If you build your applications in PHP use the Laravel library. It'll work flawlessly with Backbone when used with the RESTfull principle. Below the link to Laravel Framework and a tutorial about building RESTfull APIs:

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

http://laravel.com/

Below is a tutorial from nettuts. Nettuts has a lot of High Quality tutorials:

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


d
drobson

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.