views:

401

answers:

7

I am new to OOP Javascript and am having trouble with the this keyword and events.

What I'm trying to achieve is: I have multiple DOM objects and want to not only bind a common event to them, but keep some data about the aforementioned objects in a global container (to increase runtime performance).

So what I do is basically this:

function ClassThatDoesSomething() {
    /* keeps node ids for processing in this.init */
    this.nodes = new Array();

    /* keeps processed node data for fast access */
    this.nodeData = new Array();

    this.sthAddNodes = function(/* ids */) {
        /* appends node ids to local variable (this.nodeData) */
    }

    function init() {
        /* gathers data from all nodes that were 
           added before and stores it in this.nodeData */

        /* here, unsurprisingly, 'this' references the window element*/

        addEvent(window,'scroll',this.scroll);
    }

    function scroll() {
        /* do stuff when user scrolls the page */

        /* 'this' references the window element here too */
    }
    addEvent(window,'load',this.init);
}

Later, in the document body, I could just add this:

var Ctds = new ClassThatDoesSomething();

And further on, add DOM elements by:

Ctds.addNodes(ids);

No further implementation code would be required.

QUESTION: How to access the JS class instance in the init and scroll methods and not the window element.

It doesn't have to be through the this keyword, I know, but still I didn't come up with anything.

P.S.

  • addEvent is an extremely basic function to attach events, it's just IE/Fx friendly and does nothing else.
  • The code I'm writing is already functional, but in procedural form, I just wanted to OOP'd it.
  • As a minor sub-question, I got the impression somehow, that getter/setter methods are discouraged in javascript, is it okay if I use them?
A: 

You can use closures for that:

function ClassThatDoesSomething() {
    var self=this;
    // ...

    function init() {
        addEvent(window,'scroll',self.scroll);
    }
}
Mike Korobov
a) that's not a closure. b) this still doesn't work.
Roatin Marth
it doesn't work because I just copied 'function init()' and don't make it 'this.init = function()'. And why do you think it is not a closure?
Mike Korobov
Also it is important how is this class used. The proper code is 'var myClassInstance = new ClassThatDoesSomething(); myClassInstance.init();'
Mike Korobov
+5  A: 

One thing I notice is that neither init nor scroll is a method on the instance.

So you only need to add init and not this.init to the load event:

addEvent(window,'load',init); // No "this." needed

And similarly:

addEvent(window,'scroll',scroll);

If you do decide to move them to the class (eg this.scroll and this.init etc), you can save a reference to this and refer to it in an anonymous function passed to addEvent:

var self = this;

this.init = function() {
    addEvent(window, 'scroll', function() {
        self.scroll()
    })
};

this.scroll = function() { /* ... */ };

addEvent(window,'load',function() {
    self.init()
});

This is called a closure.

Roatin Marth
Wow, so many answers, I'm overwhelmed, thanks all. So anyway, how exactly would a method be defined? (dumb question, sorry)
Raveren
@Raveren: well that is a big question. This code sample is an example of what crockford calls "privileged" member declaration. See http://www.crockford.com/javascript/private.html That's actually an advanced approach to OOP in JavaScript. If you want to start with the basic of defining classes in JavaScript, start here: http://mckoss.com/jscript/object.htm
Roatin Marth
+2  A: 
function MyConstructor() {
    this.foo = "bar";
    var me = this;
    function myClosure() {
        doSomethingWith(me.foo);
    }
}
Jonathan Feinberg
A: 

This trick should work:

function ClassThatDoesSomething() {
...
    this.This = this;
...
}

Then inside those problematic methods you can use 'This'.

Hope this helps.

NawaMan
No, it wouldn't work.
Reinis I.
`this.This = this`. I can't even wrap my head around this.
Roatin Marth
+1  A: 

this is not determined until execution of the function. When attaching an event listener, you are passing a function, which does not carry scope with it. Therefore, on the specified event, the function is running in the scope of window, meaning that this will be equal to window. To force execution in a particular scope, you can use tricks like creating a new variable to equal this, such as:

var that = this;
...
addEvent(window,'scroll', function() {
    that.scroll()
});
geowa4
A: 

Do this:

var ClassThatDoesSomething = function() {
    /* keeps node ids for processing in this.init */
    this.nodes = new Array();

    /* keeps processed node data for fast access */
    this.nodeData = new Array();
}

ClassThatDoesSomething.prototype.sthAddNodes = function(/* ids */) {
        /* appends node ids to local variable (this.nodeData) */
    }
}

ClassThatDoesSomething.prototype.init = function() {
        /* gathers data from all nodes that were 
           added before and stores it in this.nodeData */

        /* here, unsurprisingly, 'this' references the window element*/

        addEvent(window,'scroll',this.scroll);
    }
}
ClassThatDoesSomething.prototype.scroll = function() {
        /* do stuff when user scrolls the page */

        /* 'this' references the window element here too */
    }
    addEvent(window,'load',this.init);
}
JoshNaro
+1  A: 

Add a method to the Function prototype that allows you to bind any function to any object:

Function.prototype.bind = function(object) {
   var __method = this;
   return function() {
      return __method.apply(object, arguments);
   };
};

Declare your event handlers in the instance (keeps things tidy):

function ClassThatDoesSomething() {

  this.events = {
    init: ClassThatDoesSomething.init.bind(this),
    scroll: ClassThatDoesSomething.scroll.bind(this),
    etc: ClassThatDoesSomething.etc.bind(this)
  };
  ...
}

Now whenever you reference your events, they'll be automatically bound to the class instance. e.g.:

function init() {
  addEvent(window,'scroll',ClassThatDoesSomething.events.scroll);
}
Civil Disobedient