How to set up JavaScript namespace and classes properly?
Asked Answered
Y

6

50

It seems there are so many ways to set up a JavaScript application so it is confusing as to which one is correct or best. Are there any difference to the below techniques or a better way of doing this?

MyNamespace.MyClass = {
    someProperty: 5,
    anotherProperty: false,

    init: function () {
        //do initialization
    },

    someFunction: function () {
        //do something
    }
};

$(function () {
    MyNamespace.MyClass.init();
});

Another way:

MyNamespace.MyClass = (function () {
    var someProperty = 5;
    var anotherProperty = false;

    var init = function () {
        //do something
    };

    var someFunction = function () {
        //do something
    };

    return {
        someProperty: someProperty
        anotherProperty: anotherProperty
        init: init
        someFunction: someFunction
    };
}());

MyNamespace.MyClass.init();

The first technique feels more like a class. I am coming from server-side background if this makes a difference. The second technique seems more redundant and a bit awkward, but I see this used a lot too. Can someone please help shed some light and advise the best way to move forward? I want to create a application with lots of classes talking to each other.

Yakut answered 25/7, 2012 at 14:2 Comment(4)
This isn't using OOP JS so much as using the module pattern.Auvil
Also, you've implied that the two methods are equivalent, but the second method is not valid. The way you've declared the private variables is syntactically invalid, you just want a regular var statement.Caryl
Thx I fixed the second method.Yakut
If you're interested in learning more, I would highly recommend Douglas Crockford's Javascript: The Good Parts. He not only offers a Javascript approach to classical OOP, but also explains how Javascript's more LISP-like parts can be used to create some other amazing structures. Starting from the ground-up, he explains how and why each structure works and what works best. Also, you may personally be interested in learning CoffeeScript, especially for its built-in class structure.Gurglet
T
2

Time has passed. I think the right answer now is to use modules (jsdoc typing in the sample).

Edge.mjs

/**
 * @typedef {import('./Vertex.mjs').Vertex} Vertex
 */

/**
 * An Edge of a @see Graph
 */
class Edge {
    /**
     * Create an edge.
     * @param {number} weight - The weight of the edge.
     * @param {Vertex} source - The source vertex of the edge.
     * @param {Vertex} target - The target vertex of the edge.
     */
    constructor(weight, source, target) {
        this.weight = weight
        this.source = source
        this.target = target
    }
}

export { Edge }

Graph.mjs

/**
 * @typedef {import('./Edge.mjs').Edge} Edge
 * @typedef {import('./Vertex.mjs').Vertex} Vertex
 */

/**
 * A Graph of @see Vertex and @see Edge
 */
class Graph {
    /**
     * Creates a new Graph instance.
     * @param {boolean} [isDirected=true] - Is the graph directed (true)?.
     * @param {boolean} [acyclic=true] - Is the graph acyclic (true) or can it contain cycles (false).
     */
    constructor(isDirected = true, acyclic = true) {
        /** @type {Vertex[]} */
        this.vertices = []
        /** @type {Edge[]} */
        this.edges = []
        this.isDirected = isDirected
        this.acyclic = acyclic
    }
...
export { Graph }

index.js

import { Edge } from './Edge.mjs'
import { Vertex } from './Vertex.mjs'
import { Graph } from './Graph.mjs'

export { Edge, Vertex, Graph }

somefile.js

import { Edge as Edge1, Vertex, Graph } from './index.mjs'
import { Edge as Edge2 } from './someotherEdge.mjs'

    let edge = new Edge1()
    let otherEdge = new Edge2()
...

In somefile.js you avoid the name collision yet can use them in the same file. And you avoid trying to make namespaces out of Objects or Functions and whatnot and trying to deal with the jsdoccery that ensues.

Alternatively, if you are using typescript you are already enjoying the namespace support and all the typing.

Trinatte answered 14/10, 2023 at 11:31 Comment(1)
Thx for the update. Indeed ES6 and TypeScript were game changers since the question was asked.Yakut
C
64

Do neither of those things.

Make a javascript "class":

var MyClass = function () {

    var privateVar; //private
    var privateFn = function(){}; //private 

    this.someProperty = 5;  //public
    this.anotherProperty = false;  //public
    this.someFunction = function () {  //public
        //do something
    };

};

MyNamespace.MyClass = new MyClass();

One with static vars:

var MyClass = (function(){

    var static_var; //static private var

    var MyClass = function () {

        var privateVar; //private
        var privateFn = function(){}; //private 

        this.someProperty = 5;  //public
        this.anotherProperty = false;  //public
        this.someFunction = function () {  //public
            //do something
        };
    };

    return MyClass;

})();

MyNamespace.MyClass = new MyClass();

With a "constructor" (all of the examples have a "constructor", this one just has parameters to work with):

var MyClass = function (a, b c) {

    //DO SOMETHING WITH a, b, c <--

    var privateVar; //private
    var privateFn = function(){}; //private 

    this.someProperty = 5;  //public
    this.anotherProperty = false;  //public
    this.someFunction = function () {  //public
        //do something
    };

};

MyNamespace.MyClass = new MyClass(1, 3, 4);

With all of the above you can do:

MyNamespace.MyClass.someFunction();

But you cannot do (from the outside):

MyNamespace.MyClass.privateFn(); //ERROR!
Caravel answered 25/7, 2012 at 14:5 Comment(12)
How do I handle the init or a constructor?Yakut
@Yakut the "constructor" is whatever you pass into the new MyClass(). I will add an example with a constructor.Caravel
A "constructor" doesn't have to have parameters.Brodench
@Brodench that is true. I just put it there for flair.Caravel
@Brodench I added that note to the post.Caravel
How would I avoid conflicts with another loaded library who creates a "var MyClass" in the global namespace as well? Is there no way around this and have to choose very unique names for my classes like "var TrumanMyClass = function ()..."?Yakut
Or just name ur class within your namespace ^_^ there is no reason to make it global, I just put it there as an exampleCaravel
How can you define the class directly into the namespace then? I've been struggling with this for a couple of days now - there is clearly something with the design of JavaScript I'm not grasping... :(Fescue
Learnt a lot from this answer. Thanks mate.Olmstead
Doesn't this answer need an update since ES6? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Copra
Not that I know of @Sl4rtib4rtf4st -- class is basically syntactic sugar around what exists here.Caravel
That completely defeats the purpose of a namespace, since it is still defined in the global scope!Cadmarr
C
6

The first example is simply an Object literal - it cannot be instantiated and doesn't have private members. The second example has some incorrect syntax (var someProperty: 5 should be var someProperty = 5) but is using a closure to encapsulate internal private state within a self-invoking anonymous function.

The second approach looks better for encapsulating private members, but could be made more "Object-oriented" by making it an instantiable class:

MyNamespace.MyClass = function() { ... };
MyNamespace.MyClass.prototype.someProperty = 'foo';

Then you can instantiate it with the 'new' keyword:

var aClass = new MyNamespace.MyClass();
aClass.init(...);
Coyle answered 25/7, 2012 at 14:9 Comment(0)
E
6

Why you should never use

 return { methodName : methodDelegate}

like in second example:

MyNamespace.MyClass = (function () {
    var someProperty = 5;

    var init = function () {
        //do something
    };

    return {
        someProperty: someProperty
        someFunction: someFunction
    };
}());

MyNamespace.MyClass.init();

When you use namespace you have to think about it as about declaration, not the instance.

MyNamespace = {};
MyNamespace.sub = {};
MyNamespace.anotherSub = {};
MyNamespace.sub.MyClass = (function () {

    var static_var; //static private var

    var MyClass2 = function () {

        var privateVar; //private
        var privateFn = function () { }; //private 

        this.someProperty = 5;  //public
        this.anotherProperty = false;  //public
        this.someFunction = function () {  //public
            //do something
        };
    };

    return MyClass2;

})();
debugger;

var c1 = new MyNamespace.sub.MyClass();
c1.someProperty = 1; // creates 5->1.

var c2 = new MyNamespace.sub.MyClass();
c2.someProperty = 2;  // creates 5->2. c1 is still 1



debugger;
var myClass = function () {
    var someProperty = 5;
    var anotherProperty = false;

    var init = function () {
        //do something
    };

    var someFunction = function () {
        //do something
    };

    return {
        someProperty: someProperty,
        anotherProperty: anotherProperty,
        init: init,
        someFunction: someFunction
    };
};


MyNamespace.MyClass = myClass();
var c2 = MyNamespace.MyClass;
// how  are planning to create one more object, while it's a reference? copy      //the whole one?

c2.someProperty = 2; // changes 5 -> 2
var c3 = MyNamespace.MyClass.init(); // create 2 instead of 5

c3.someProperty = 3;    // changes c3 and c3 from 2 to 3.
console.log(c2.someProperty + c3.someProperty);

And no metter how much Module anti-patter was popular. Declaration gives you an ability to use the same code with different instances in an expected way for other developers . The quality of code and simplicity of its reading increases. The goal of any developer is to write a simple code to be read, not a shorter or D.R.Y. - but simple to be read and be understanded by another developer. That decreases the number of bugs first. (c) S. McConnell

Ethelethelbert answered 30/1, 2015 at 21:50 Comment(0)
A
4

I use the following syntax for the instantiable classes with namespace

 var MYNamespace = MYNamespace|| {};

 MYNamespace.MyFirstClass = function (val) {
        this.value = val;
        this.getValue = function(){
                          return this.value;
                       };
    }

var myFirstInstance = new MYNamespace.MyFirstClass(46);
alert(myFirstInstance.getValue());

jsfiddle: http://jsfiddle.net/rpaul/4dngxwb3/1/

Agricola answered 10/12, 2014 at 0:5 Comment(0)
W
2

How to combine namespace and class declaration:

var ns = { // your namespace
    my_value: 1, // a value inside the namespace to avoid polluting
    MyClass: function() { // a class inside the namespace
        this.class_property: 12,
        this.class_method: function() {
            console.log("My property: " + this.class_property);
        }
    },
    myFunction: function() { // a function inside a namespace
        console.log("I can access namepsace value if you don't use 'new': " + this.my_value);
    }
};

Accessing your value:

console.log(ns.my_value);

Now, for the Class and Function: If you use new, the word this inside the function will point to it's constructor. If you don't use new, this will point to the namespace. So,

Using a Class:

var obj = new ns.MyClass();

Using the Function:

ns.myFunction();

If you construct the object without new, this will point to the namespace, so the namespace will be "destroyed" because MyClass.class_property and MyClass.class_method will be added to it.

Wind answered 28/9, 2016 at 14:25 Comment(0)
T
2

Time has passed. I think the right answer now is to use modules (jsdoc typing in the sample).

Edge.mjs

/**
 * @typedef {import('./Vertex.mjs').Vertex} Vertex
 */

/**
 * An Edge of a @see Graph
 */
class Edge {
    /**
     * Create an edge.
     * @param {number} weight - The weight of the edge.
     * @param {Vertex} source - The source vertex of the edge.
     * @param {Vertex} target - The target vertex of the edge.
     */
    constructor(weight, source, target) {
        this.weight = weight
        this.source = source
        this.target = target
    }
}

export { Edge }

Graph.mjs

/**
 * @typedef {import('./Edge.mjs').Edge} Edge
 * @typedef {import('./Vertex.mjs').Vertex} Vertex
 */

/**
 * A Graph of @see Vertex and @see Edge
 */
class Graph {
    /**
     * Creates a new Graph instance.
     * @param {boolean} [isDirected=true] - Is the graph directed (true)?.
     * @param {boolean} [acyclic=true] - Is the graph acyclic (true) or can it contain cycles (false).
     */
    constructor(isDirected = true, acyclic = true) {
        /** @type {Vertex[]} */
        this.vertices = []
        /** @type {Edge[]} */
        this.edges = []
        this.isDirected = isDirected
        this.acyclic = acyclic
    }
...
export { Graph }

index.js

import { Edge } from './Edge.mjs'
import { Vertex } from './Vertex.mjs'
import { Graph } from './Graph.mjs'

export { Edge, Vertex, Graph }

somefile.js

import { Edge as Edge1, Vertex, Graph } from './index.mjs'
import { Edge as Edge2 } from './someotherEdge.mjs'

    let edge = new Edge1()
    let otherEdge = new Edge2()
...

In somefile.js you avoid the name collision yet can use them in the same file. And you avoid trying to make namespaces out of Objects or Functions and whatnot and trying to deal with the jsdoccery that ensues.

Alternatively, if you are using typescript you are already enjoying the namespace support and all the typing.

Trinatte answered 14/10, 2023 at 11:31 Comment(1)
Thx for the update. Indeed ES6 and TypeScript were game changers since the question was asked.Yakut

© 2022 - 2024 — McMap. All rights reserved.