4
\$\begingroup\$

Is this a good way to create a class with a constructor and namespace in object literal style?

// root namespace
var myApp = myApp || {};

// sub namespace
myApp.model = {
    // Constructor
    Person: function (name) {
        this.name = name;
    }
};


myApp.model.Person.prototype = {
    sayName: function () {
        alert(this.name);
    },
    sayHi: function () {
        alert("Hi, " + this.name);
    }
};

var p1 = new myApp.model.Person("CK");
p1.sayName();
p1.sayHi();

JSHint shows no error and the full source code is here.

\$\endgroup\$
7
  • \$\begingroup\$ I may be incorrect, but wouldn't your myApp.model.Person.prototype only add those functions to the Person objects that are a part of your myApp.model, and not all Person objects? If that is intended, that's fine. I could just see that becoming an issue later on. Do you have a separate Person object? \$\endgroup\$
    – krillgar
    Sep 9 '14 at 13:05
  • \$\begingroup\$ I am from Java background. I want to make myApp.model as my package, the fully qualified class name I want to make is myApp.model.Person. This class shall have a constructor that take String name as parameter, and have 2 methods sayName and sayHi. It is simple to be achieved in Java, but I am not sure how to make it right in Javascript. \$\endgroup\$ Sep 9 '14 at 13:15
  • \$\begingroup\$ @krillgar, there is no other Person object. Why it could be an issue if I define my qualified class name as myApp.model.Person? \$\endgroup\$ Sep 9 '14 at 13:59
  • 1
    \$\begingroup\$ @krillgar: if you make a 2nd instance of myApp.model.Person (with different constructor arg), it'll work ok: jsfiddle.net/vj4ujz0L/3 \$\endgroup\$ Sep 9 '14 at 16:25
  • 1
    \$\begingroup\$ @krillgar You needn't be concerned. JavaScript doesn't care where the Person constructor is stored, whether it's myApp.model.Person or anywhere else. All Person instances which share that constructor will have access to the functions attached to the constructor's prototype. \$\endgroup\$
    – Keen
    Sep 12 '14 at 18:09
3
\$\begingroup\$

This looks good to me. From a once over;

  • Consider using 'use strict'
  • You cannot have 'private' functions or properties, that should be fine
  • Naming is fine. As I personally dislike namespaces I would probably go for a 1 character namespace for myApp.
  • Indenting is fine
  • 0 comments, you might want to consider one-liner comments separating the different sections of your code.
  • Somewhat to krillgar's point, instanceof checks will have to check against myApp.model.Person with your approach, this should be fine as well

All in all, JavaScript is not Java. I would not go so far as to say that namespaces are not idiomatic, but I would invite you read this.

\$\endgroup\$
5
  • \$\begingroup\$ If the listed code is under Person.js file, I would like to add another class myApp.model.Facility at another Facility.js file. How should I define the sub namespace block? This may worth for another question, but hope you can answer as well. \$\endgroup\$ Sep 9 '14 at 13:32
  • 1
    \$\begingroup\$ @CK by combining your first line with my answer, ie myApp.model = myApp.model || {}; followed by myApp.model.Person = /*...*/ rather than myApp.model = { Person /*...*/ \$\endgroup\$ Sep 9 '14 at 13:36
  • \$\begingroup\$ I disagree re: comments - comments should only be added to code that is hard to understand without the comment. Assuming that this code will be read by people who know javascript, no comments are needed in this code. \$\endgroup\$
    – TV's Frank
    Sep 9 '14 at 14:48
  • \$\begingroup\$ @TV'sFrank It's an opinion, I would invite you to look at CK Lee's answer, the comments make it more readable in my mind. \$\endgroup\$
    – konijn
    Sep 9 '14 at 14:54
  • \$\begingroup\$ @CKLee You should look up "javascript module pattern". You'll find a pattern for isolating your new variables from the global namespace, and within that context you can safely alias your namespaces to something convenient like this: var Person = myApp.model.Person. \$\endgroup\$
    – Keen
    Sep 12 '14 at 19:47
4
\$\begingroup\$

I would suggest splitting the addition of the methods as follows:

myApp.model.Person.prototype.sayName = function () {
    alert(this.name);
};

myApp.model.Person.prototype.sayHi = function () {
    alert("Hi, " + this.name);
};

This way you can define additional methods in different places (if needed), as you're not replacing the entire prototype in one go.

\$\endgroup\$
5
  • \$\begingroup\$ In Java, normally we define all methods under public class Person { ... } block in a same file. Should it be good to put all related methods in the same place in Javascript as well? \$\endgroup\$ Sep 9 '14 at 14:03
  • \$\begingroup\$ Yes, it would be normal to group them together for ease of maintenance, but nothing actually stopping you in the language from putting them wherever you want \$\endgroup\$ Sep 9 '14 at 14:48
  • 1
    \$\begingroup\$ I might be wrong, but I don't understand how the OP's solution doesn't allow you to later on add extra functions to the prototype (something like this: jsfiddle.net/vj4ujz0L/6) \$\endgroup\$ Sep 9 '14 at 16:30
  • 1
    \$\begingroup\$ ShivanDragon is right. But I'd still recommend following James Thorpe's advice and adding your new methods to the prototype that's already there, since that approach works nicely with inheritance models. Oh, and please never add methods to a single prototype at multiple different places (unless your project is very small or you have a very clear set of conventions to which everyone agrees). \$\endgroup\$
    – Keen
    Sep 9 '14 at 17:48
  • \$\begingroup\$ @ShivanDragon Yes, you can add them to the prototype, so long as you do so after you've already done the original assignment. If you add each method individually, it can make refactoring easier as you don't need to ensure they're added after the initial assignment \$\endgroup\$ Sep 10 '14 at 8:38
1
\$\begingroup\$

Based on comment from konijin, here is the updated version

// root namespace
var myApp = myApp || {};

// sub namespace
myApp.model = myApp.model || {};

// constructor
myApp.model.Person = function (name) {
    this.name = name;
}

myApp.model.Person.prototype = {
    sayName: function () {
        alert(this.name);
    },
    sayHi: function () {
        alert("Hi, " + this.name);
    }
};

var p1 = new myApp.model.Person("CK");
p1.sayName();
p1.sayHi();

alert(p1 instanceof myApp.model.Person); // true
alert(p1 instanceof Object); // true
alert(p1 instanceof Person); // Uncaught ReferenceError: Person is not defined

http://jsfiddle.net/cklee75/1d6jgx4k/3/

\$\endgroup\$
1
\$\begingroup\$

When you assign an object to a prototype it changes the prototypal inheritance of that object. The prototype of the object will no longer have the original constructor function and it will point to Object instead!

This affects your code, as it is evidenced by adding the following line:

console.log(p1.constructor === Object); // returns: true

Two ways to correct this:

A. Assign the functions directly. Don't wrap them in an object. That would look like this:

myApp.model.Person.prototype.sayName = function(){
   alert(this.name);
};

myApp.model.Person.prototype.sayHi = function(){
    alert("Hi, " + this.name);
};

B. Reassign the constructor to your object. You can do that by adding a line of code into your object, like this:

myApp.model.Person.prototype = {
    constructor: myApp.model.Person, //  <========
    sayName: function () {
        alert(this.name);
    },
    sayHi: function () {
        alert("Hi, " + this.name);
    }
};

Which is the same as this:

myApp.model.Person.prototype.constructor = myApp.model.Person;

It's worth noting that restoring the constructor in this manner creates a property with enumerable set to true. Native constructors are not enumerable by default. So if you're using ES5, this might be the preferred way to restore the constructor:

Object.defineProperty(myApp.model.Person.prototype, 'constructor', {
enumerable: false,
value: myApp.model.Person
});

More reading on this: http://javascript.info/tutorial/constructor

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.