5

Could someone explain this to me?

var diagramImage = new Kinetic.Shape(function () {
    var context = this.getContext();
    context.beginPath();
    context.lineWidth = 1;
    //This is crazy tricks. It's part of the KineticJS demo website, but how am I able to assign diagramImage.color here?
    context.strokeStyle = diagramImage.color;

    var lastVertice = polygon.Vertices[polygon.Vertices.length - 1];

    context.moveTo(lastVertice.X, lastVertice.Y);

    for (var i = 0; i < polygon.Vertices.length; i++) {
        var vertice = polygon.Vertices[i];
        context.lineTo(vertice.X, vertice.Y);
    }

    context.stroke();
    context.closePath();
});

It seems to me that diagramImage does not exist until the Kinetic constructor returns, but I am able (and seem to need to) assign context's strokeStyle to diagramImage's color -- before diagramImage has been created? Why does this work?

EDIT: Full code:

function DrawPolygon(diagramLayer, polygon) {
    var diagramImage = new Kinetic.Shape(function () {
        var context = this.getContext();
        context.beginPath();
        context.lineWidth = 2;
        //This is crazy tricks. It's part of the KineticJS demo website, but how am I able to assign diagramImage.color here?
        context.strokeStyle = diagramImage.color;

        var lastVertice = polygon.Vertices[polygon.Vertices.length - 1];

        context.moveTo(lastVertice.X, lastVertice.Y);

        for (var i = 0; i < polygon.Vertices.length; i++) {
            var vertice = polygon.Vertices[i];
            context.lineTo(vertice.X, vertice.Y);
        }

        context.stroke();
        context.closePath();
    });

    diagramImage.color = "red";

    diagramImage.on("mouseover", function () {
        this.color = "green";
        diagramLayer.draw();
    });

    diagramImage.on("mouseout", function () {
        this.color = "red";
        diagramLayer.draw();
    });

    diagramLayer.add(diagramImage);
    planViewStage.add(diagramLayer);
}
Sean Anderson
  • 27,963
  • 30
  • 126
  • 237

3 Answers3

8

Because where you are calling diagramImage.color is within a closure / function that is passed in to the Kinetic.Shape constructor. This function is not called / is not executed by the constructor until after the new instance created by the constructor is assigned to diagramImage.

Here's a minimal example that may better explain what's happening:

var MyObject = function(f){
  this.myFunc = f; // f is executed sometime later...
};
MyObject.prototype.execute = function(){
  this.myFunc();
};

var myObjInst = new MyObject(function(){
  console.log("myObjInst:", myObjInst);
});
myObjInst.execute();

As Twisol noted, this can be improved by using this instead. For example:

(function(){
  var MyObject = function(f){
    this.myFunc = f; // f is executed sometime later...
  };
  MyObject.prototype.execute = function(){
    this.myFunc();
  };

  var myObjInst = new MyObject(function(){
    console.log("myObjInst:", this);
  });
  myObjInst.execute();
})();

However, as Chris noted, unless documented by the API - there is no guarantee that this will refer to Kinetic.Shape during the callback - so continuing to use diagramImage here may still be the better of these 2 options.

In short, I think this is not the best API / example / use of JavaScript - and I would not consider this a nuance of JavaScript that you should have to deal with. Sure, these nuances are there if you need them - but you don't have to.

ziesemer
  • 27,712
  • 8
  • 86
  • 94
  • Is there any way I can make this code more... intuitive? Or is this a nuance of Javascript that I will have to learn to love? :) – Sean Anderson Feb 14 '12 at 17:06
  • Source (literally) [here](http://www.kineticjs.com/download/kinetic-v3.7.3.js). `drawFunc` is called neither in `Kinetic.Shape` nor `Kinetic.Node`, but the fact that it's called `drawFunc` and kept as a property is telling. – Twisol Feb 14 '12 at 17:09
  • 1
    @SeanAnderson: Yes, use `this` in the function instead of `diagramImage`. That's okay to do here because where `drawFunc` *is* called, it provides the right object as `this`. – Twisol Feb 14 '12 at 17:11
  • @ziesemer: Your first example prints `myObjInst: undefined` as well for me. In both cases, you're calling the provided method from the constructor, which is not what Kinetic.js does - it stores it for later. – Twisol Feb 14 '12 at 17:13
  • @Twisol - Sorry, I over-simplified my example. Answer adjusted to reflect this. – ziesemer Feb 14 '12 at 17:18
  • @ziesemer: But now both examples work perfectly well! At the time `myFunc` is called, `myObjInst` has been assigned to and *is within `myFunc`'s scope of closure*. – Twisol Feb 14 '12 at 17:25
  • I'm not sure I agree that this is a 'nuance' that you shouldn't have to deal with. Capturing variables for use in a closure is normal, idiomatic Javascript. Unless you're guaranteed by the lib's API that `this` == the `Kinetic.Shape` instance during the invocation of the callback, then referring to `diagramImage` when you really mean `diagramImage` very much is the better thing to do. – Chris Subagio Feb 14 '12 at 20:39
1

That's an interesting construct. What's happening appears to be:

  • diagramImage is a reference before it's assigned anything just by virtue of the declaration. To visualize this, imagine that var diagramImage was on the previous line by its self.
  • Kinetic.Shape takes a callback, that anonymous function, as one of its constructor arguments to use later.
  • The callback wants to refer to the Kinetic.Shape object. Presumably there is some contract that describes what this refers to later (as evinced by the this.getContext() use), and it isn't the Kinetic.Shape object.
  • Because diagramImage is a reference, and by the time the reference will be used, it will have been assigned the new Kinetic.Shape, it's kosher to use it for said purpose.

In principle, this is no different from the usual pattern of using a local variable to make the current this available in a callback, e.g.

var self = this;
$('myelement').click( function(){ self.hi = true; } );

It's just that here, the variable being made available for later isn't the current object, it's a member of said object.

Chris Subagio
  • 6,099
  • 1
  • 15
  • 7
0

I think this post may help explain it - http://www.quirksmode.org/js/associative.html

Particularly the section on Associative Arrays. The write explains that objects in javascript are also considered associative arrays.

So event though diagramImage.strokeStyle may not be explicitly defined, you can still reference diagramImage['strokeStyle'].

Does that help?

shanabus
  • 12,989
  • 6
  • 52
  • 78