1

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?

einord
  • 2,278
  • 2
  • 20
  • 26
  • There are no "fields" in javascript. [One is a property, to other a variable](http://stackoverflow.com/q/13418669/1048572). You have to decide [which one you want to use](http://stackoverflow.com/q/25365855/1048572). – Bergi Aug 19 '14 at 12:54
  • What's that IIFE around the `innerClass` good for? It can be omitted safely in your case. Also, you shouldn't use an `init` method, just use the *constructor* that you have there. – Bergi Aug 19 '14 at 12:55
  • @bergi - yes his innerClass is of no use. I have provided him with an answer without that – wallop Aug 19 '14 at 12:57

4 Answers4

0

I don't understand what you do that much complicated things to have classes.

var myField and <returned object>.myField are two different variables, modifying one won't change the other.

You can try this (encapsulation):

return {
    init: this.init,
    myFunction: this.myFunction,
    getMyField: function() {return myField;},
}
// ...
$('.console').append('<br />Outside myFunction: ' + myObject.getMyField());

or this (get operator):

return {
    init: this.init,
    myFunction: this.myFunction,
    get myField() {return myField;},
}
// ...
$('.console').append('<br />Outside myFunction: ' + myObject.myField);
Volune
  • 4,324
  • 22
  • 23
  • Please use the term *property*, not *variable* for object properties. – Bergi Aug 19 '14 at 12:58
  • True, but I don't think using the proper terms will make my point about _changing one doesn't change the other_ more understandable. – Volune Aug 19 '14 at 13:19
0

This worked fine for me

 $('.console').append('Class simulation test:<br />');

// My class
function MyClass() {    

        var self = this, myField;

        // This function works as the constructor
        this.init = function(opts) {
            // Arguments to the constructor
            $('.console').append('<br />Inside myFunction: ' + JSON.stringify(opts));
            var defaultOpts = {
                myInitArgument: null
            }
            opts = $.extend(defaultOpts, opts);
            //self = this; // no need of this

            // Any custom constructor code is generated here...
            this.myFunction('Hello from the constructor!');
        }

        // A function
        this.myFunction = function(value) {

         this.myField = value;        //if you dont use var it will either refer to parent my field or window 
            $('.console').append('<br />Inside myFunction: ' + this.myField);
        };
        console.log(JSON.stringify(arguments[0]));
        this.init(arguments[0]);
        // 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 {

            myFunction: this.myFunction,
            myField: myField,
        }

}

// instanciate
var myObject = new MyClass({myInitArgument: 'test'});


// test
myObject.myFunction('Hello from the outside!');
$('.console').append('<br />Outside myFunction: ' + myObject.myField);
wallop
  • 2,510
  • 1
  • 22
  • 39
0

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
pp19dd
  • 3,625
  • 2
  • 16
  • 21
  • Nice approach. How does it work when working with events? Like if I add a jQuery click delegate like this in the init-function: $('.button').click(function() { var x = that.getEarnings(); }); Would 'that' still be available as the same object? – einord Aug 19 '14 at 13:19
  • 1
    You always need to tie a variable (apple/orange) to an event somehow. For example: `var apple = new Fruit();` first, then refer to it by its actual instance afterward: `$(".button-more").click(function() { apple.setQuantity(apple.count+1); });` – pp19dd Aug 19 '14 at 14:08
  • What is the benefit of `for( k in opts )(function(k, v) {that[k] = v;})(k, opts[k]);` over `$.extend(this, opts);` or a simple `for(var k in opts) this[k] = opts[k];`. And why no `var k`, `k` is a global here, right ? – Volune Aug 19 '14 at 14:42
  • @Volune: my example is generic enough to where it doesn't require jQuery. – pp19dd Aug 19 '14 at 14:57
  • @pp19dd What about `for(var k in opts) this[k] = opts[k];` and the missing `var` ? – Volune Aug 19 '14 at 15:04
  • It's a bit sloppy, I confess. ;) – pp19dd Aug 19 '14 at 15:23
  • WTH do you declare local `var genus, cost, count;` variables in your `Fruit` constructor but never use them? – Bergi Aug 19 '14 at 16:02
  • @Bergi: you're right, this is not a complete example of OOP construction, but, specifically I put it there as a preamble -- a mental hint that those are common traits that should be expected elsewhere in the code. – pp19dd Aug 19 '14 at 16:37
  • But it makes no sense? If you want to default-create properties, you should create *properties*, not local variables. – Bergi Aug 19 '14 at 16:49
  • Bergi, I stand corrected. Should have moved those into the init() routine. Thanks for catching that. – pp19dd Aug 19 '14 at 17:09
0

I have recently been researching this. I have succeeded, here. The ticker object at that link is a real psuedo class.

var foo = function(args){
    this.args = args;
    this.whatever = "whatever";
}
foo.prototype.addBar = function(bar){
    this.args += bar;
}
foo.prototype.getArgs = function(){
    return this.args;
}

var baz = new foo("Hello");
baz.addBar(" Super Man");
var helloStr = baz.getArgs();
//helloStr holds "Hello Super Man"
var what = baz.whatever;
//what holds "whatever"

Simple, no need for inner function, new foo() is the constructor.

Community
  • 1
  • 1
Jon Crawford
  • 193
  • 11