0

I'm trying to get a grip on "OOP" JavaScript techniques, and I began writing a small test application today. Basically, it's a game loop and on each update coordinates are to be increased so that an HTML element moves.

The problem is I want to be able to run more than one instance of the app, and therefore I'm trying to store the instance data in this, but what is saved in my constructor and exec() method is not available in the private update() method. What seems to be the officer, problem?

var Jsloth = (function () {
    var Jsloth = function () {
        var sloth = document.createElement('div');
        var attr = document.createAttribute('class');
        attr.value = 'sloth';
        sloth.setAttributeNode(attr);
        this.sloth = document.getElementsByTagName('body')[0].appendChild(sloth);
    };

    var exec = function () {
        this.x = 0;
        this.y = 0;
        var that = this;
        setInterval(function () {
            that.update();
        }, 1000/10);
    };

    var update = function () {
        this.x++;
        this.y++;
        this.sloth.style.left = this.x + 'px';
        this.sloth.style.bottom = this.y + 'px';
    };

    Jsloth.prototype.constructor = Jsloth;
    Jsloth.prototype.exec = exec;
    Jsloth.prototype.update = update;

    return Jsloth;
})();

var sloth1 = new Jsloth();
sloth1.exec();

Edit: Updated code with a working solution!

Viktor
  • 487
  • 2
  • 8
  • 26
  • 1
    If you declare functions with function declaration statements, you can give them names that will show up in stack traces when you're debugging. There's really no reason to declare them with anonymous function expressions like you've got for "exec", "update", and the inner "Jsloth". – Pointy May 04 '13 at 23:02
  • `function exec() { /* ... */ }` -- I agree it's confusing; this trend of declaring functions as you did started showing up in blog posts etc. a couple years ago. It does in fact work just fine, but it's ... well it drives me crazy, personally :-) – Pointy May 04 '13 at 23:32
  • Haha. I have acquired Crockford's book, hope it puts me straight. When doing `function exec() { /* ... */ }`, can I still use my namespace `Jsloth` and `prototype`? Bah, in Java there's only one way... When looking up JS, there are 8 (tilt it!) ways. – Viktor May 04 '13 at 23:39

2 Answers2

3

In JavaScript, this is (almost) entirely decided by how you call a function, not where/how it's defined.

The way you're calling update:

update();

...this will be the global object (window on browsers), or undefined if you use "use strict".

To set this within update, use call or apply:

update.call(this);
// or
update.apply(this);

More (on my blog):

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
2

You've not added update to the prototype. The value of this in that method will be most likely the window object.

Change your call from this:

update();

To this:

update.call(this);

Or add update to the .prototype:

Jsloth.prototype.update = update;

and use:

this.update();

But if you're going to call update() from the setInterval(), you'll need to ensure the proper this value.

To do that, you can pass an anonymous function, and keep a reference to the outer this value in a variable.

var exec = function () {
    this.x = 0;
    this.y = 0;
    var that = this;
    setInterval(function() {
        that.update();
      //update.call(that); // if you didn't add update() to the .prototype
    }, 1000/10);
};
  • Perfect! My Java instincts told me to not add `update()` to `prototype`, since it was not for public access, but maybe there's no harm in that. It works now, but I cannot create two instances for some reason. The second one will not show up in the DOM... Do you know why? – Viktor May 04 '13 at 23:23
  • @squint NB: don't forget that it's possible to create per-instance methods (as properties) without them being on the prototype, and they'll still require `obj.method()` calling syntax. – Alnitak May 04 '13 at 23:25
  • @Viktor: Not sure why. As long as each new element is created every time the constructor is invoked, it should work. If you tried to re-append the same element, it would just be moved. You didn't put the element on the prototype, did you? –  May 04 '13 at 23:31
  • @squint: Nevermind, it works now! I must have misused `setTimeout()`. :) – Viktor May 04 '13 at 23:34
  • @Alnitak: Yep, or could create a new `update()` on each object with a bound `this`, and do `setInterval(this.update, 1000)` –  May 04 '13 at 23:36
  • 1
    @Viktor: No, what you can do is use `Function.prototype.bind` to create a new `update` for each object you construct where the `this` value of each function will be bound to each object. So you'd keep the `var update = func...`, but then in the constructor, you could do `this.update = update.bind(this);`. So then the object represented by `this` will be bound as the `this` in the invocation of that `update` function. That allows you more flexibility in calling each object's `update()`, like my example two comments up, or like `var u = this.update; u();`. –  May 04 '13 at 23:46
  • @squint: Aha. I will give that a try tomorrow, but now the time nears 2AM and I'm off to bed. Thank you again for your help! – Viktor May 04 '13 at 23:52