einord einord - 14 days ago 8
Javascript Question

Trying to simulate class in javascript, but cannot reach variables inside

I am generating a lot of "classes" (actually functions trying to simulate classes as in c# or other object oriented languages), and are looking for the best way to do this.
As you might notice, I also have jQuery available.

This is how all classes are generated at this point:

MyClass = (function() {
function innerClass() {

var self = this;
var myField;

// This function works as the constructor
this.init = function(opts) {
// Arguments to the constructor
var defaultOpts = {
myInitArgument: null
}
opts = $.extend(defaultOpts, opts);
self = this;

// Any custom constructor code is generated here...
}

// A function
this.myFunction = function() {
myField = "Hello World!";
}

// Returns an object with all selected fields and function that should work as "public". Those not mentioned here, will not be visible outside this class.
return {
init: this.init,
myFunction: this.myFunction,
myField: myField,
}
}
return innerClass;
})();


Then I create instances of the class like this:

var myObject = new MyClass();
myObject.init({myInitArgument: 'test'});


My main problem here is that inside the myFunction, "myField" will be set to "Hello World!" if I break in the debugger (i.e. Chrome Developer Tools), but using "myObject.myField" returns undefined.

I made a fiddle if you would like to play around with this sample.

What is the best way to accomplish this problem, and are there perhaps other things you feel of warning me about?

Answer

JavaScript is a bit weird when it comes to making classes and objects. IMO, this is the most reliable and readable method of doing it: start with a function that becomes your primitive object (Fruit).

Edit: thanks to @Bergi for pointing out that previous version had vestigial variables, needed to be moved to init().

function Fruit(opts) {
    this.init(opts);
}

Now, expand the function, giving it more functions, like init, etc:

Fruit.prototype.init = function(opts) {
    // init values go here
    this.cost = 0;
    this.count = 0;

    var that = this;  // in the iteration below, we want to refer to our parent
    for( k in opts )(function(k, v) {
        that[k] = v;
    })(k, opts[k]);
}

// now, here's a specialized set of functions that sets properties (price/quant)
// note that they do "return this" - this is so you can conveniently chain
// commands. ex: apple.setPrice(10).setQuantity(5);
Fruit.prototype.setPrice = function(how_much) {
    this.cost = how_much;
    return(this);
}

Fruit.prototype.setQuantity = function(how_many) {
    this.count = how_many;
    return(this);
}

Simple function to return a computed value. At this point, once instantiated, the object becomes 'self aware'. Helper functions like this become more readable.

Fruit.prototype.getEarnings = function() {
    return( this.cost * this.count );
}

So far we've only setup the abstract structure. To use this, create a new object:

var apple = new Fruit({ genus: 'Malus' });
var orange = new Fruit({ genus: 'Citrus' });

apple.setPrice(1.50).setQuantity(20);
orange.setPrice(1.25).setQuantity(40);

console.info( apple.genus + " will earn you $" + apple.getEarnings() );   // $30
console.info( orange.genus + " will earn you $" + orange.getEarnings() ); // $50