2

Trying to do an .onload method with an Image() object and apparently it isn't inheriting the "this" in its function. Any help?

function UI() {
    this.canvas_obj = document.getElementById('game');
    this.canvas = this.canvas_obj.getContext('2d');
    this.imgcache = {};
    this.imglist = [
        'rooms/main-square.png'
    ];
    for (var i = 0; i < this.imglist.length ; i++) {
        var img = new Image();
        img.src = this.imglist[i];
        this.imgcache[this.imglist[i]] = img;
    }
}

// snip //

/*
 * drawImg
 * Draws an image on the canvas at the specified x, y (if the image isn't in the pre-cache, it creates it as well)
 * @param str   image path
 * @param array x,y
 * @param array width, height
 */
UI.prototype.drawImg = function(path, coords, size) {
    var found = false;
    if (size == undefined) {
        var w = 0;
        var h = 0;
    } else {
        var w = size[0];
        var h = size[1];
    }
    for (var i = 0; i < this.imgcache.length ; i++) {
        if (path == this.imgcache[i].src) {
            found = true;
        }
    }
    if (!found) {
        var img = new Image();
        img.src = path;
        this.imgcache[path] = img;
    }
    if (w == 0 && h == 0) {
        this.imgcache[path].onload = function() {
            this.canvas.drawImage(this.imgcache[path], coords[0], coords[1], this.imgcache[path].width, this.imgcache[path].height);
        };
    } else {
        this.imgcache[path].onload = function() {
            this.canvas.drawImage(this.imgcache[path], coords[0], coords[1], w, h);
        };
    }
}
Tom van der Woerdt
  • 29,532
  • 7
  • 72
  • 105
d0ctor
  • 31
  • 1
  • 3
  • 3
    One other point in your code. If you're expecting onload to always get called for an image, you have to set the onload handler before you set `.src` because onload may fire immediately when you set `.srv` if the image is coming from the browser cache. – jfriend00 Sep 08 '11 at 02:44

2 Answers2

3

As for every variable, in JavaScript, the scope of this is relative to function you are in. So, when in

this.imgcache[path].onload = function() {
     // ...   
};

this will be bound to the object imgcache[path]. A common approach is to keep the value of this in another variable (by convention, it's often that) in order to access it inside nested functions: it's called a closure.

var that = this;

this.imgcache[path].onload = function() {
     // that will reference the "outer this"
};

Now, this is due to how JavaScript binds the this value on function invocation. It is possible to bind this to another value by using call or apply.

Community
  • 1
  • 1
Bryan Menard
  • 13,234
  • 4
  • 31
  • 47
  • For sake of completion, it's worth mentioning bind (https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind). – Corbin Sep 08 '11 at 02:56
  • 1
    The value of a function's *this* keyword has **nothing** to do with scope. It is controlled **only** by how a function is called (except for ES5 bind, but that can be ignored for now). – RobG Sep 08 '11 at 03:12
  • @RobG Since JavaScript has only function scope for variables, I think it's the easiest way to explain how binding works without going into the subtleties of the language. – Bryan Menard Sep 08 '11 at 03:56
  • *this* is always bound to the current execution context (function or global). Probably just quibbling over words, but if you want to keep it simple, don't mention scope at all in relation to *this*. – RobG Sep 08 '11 at 06:06
0

The value of a function's this keyword is set by how the function is called. When the drawImg method is called on an instance of UI, e.g.:

var fooUI = new UI(...);
fooUI.drawImg(...);

then in the method fooUI.drawImg, the this keyword will reference fooUI.

Within the method there is an assignment to an onload property:

    this.imgcache[path].onload = function() {
        this.canvas.drawImage(this.imgcache[path], coords[0], coords[1],
                              this.imgcache[path].width, this.imgcache[path].height);
    };

The first this will reference fooUI. However, the anonymous function assigned to the onload property is called as a method of the object referenced by this.imagecache[path], so that is the object referenced by the function's this when called.

You can change that by using a closure to a local variable with a sensible name (that is not a good choice in my opinion) like:

    var uiObj = this;
    this.imgcache[path].onload = function() {
        uiObj.canvas.drawImage(uiObj.imgcache[path], coords[0], coords[1],
                               uiObj.imgcache[path].width, uiObj.imgcache[path].height);
    };
RobG
  • 142,382
  • 31
  • 172
  • 209