JavaScript Day 13: Rewriting WireLibJS

One question I had early on was along the lines of “What if I want to have two canvases with wirelib animations in them?” The answer was, you couldn’t. Because the wirelib object was a sort of singleton, initialized with a single canvas and that was that. That might be ok for some implementations, but I could definitely see the use case, and it was an interesting exercise to convert it to work that way.

It seemed to me the best way to have multiple instances of the library, each initialized with its own canvas, was to use a constructor function. Then you could do something like:

var wl1 = new Wirelib(myCanvas1);
var wl2 = new Wirelib(myCanvas2);

Then you could add different lines to each one, and animate them separately.

Well, it took a bit of work, but that’s what I got going. The architecture is a bit different, so it’s a good chance to explore different ways of doing things.

You can see the result here: http://www.bit-101.com/jscanvas/wirelib_0.1.js

Note that the whole thing is now contained in a single function definition:

[php lang=”JavaScript”]function Wirelib(canvas) {
//…
}[/php]

This really brings back some good old AS1 memories. Remember, in JS, a function is an object. And you can use the “new” keyword with any function to make a new object that will use that function as its prototype. Doing this, you can simulate all kinds of Object Oriented, class based setups. But in reality, JS does not have classes, so all you are doing is simulating such. We did this in AS1 and while it was useful and led to AS2, which standardized the syntax, and AS3 which really DOES have classes, it always felt pretty hacky back then. I was pleased to read from a few sources that this kind of pseudo-class stuff is often frowned upon. From what I’ve seen, it’s most often implemented by people coming into JavaScript from an object oriented language like AS3. Not being comfortable with the classless system, and not wanting to learn the lay of the land and the way most other JS programmers do things, they insist on simulating classes and inheritance and probably cause more problems than they are solving.

Anyway, simply using a constructor function does not fall into this category. It’s a valid part of the core JavaScript language and not a hack. Here, the constructor function takes a canvas as an argument.

Note that all the functions now are redefined to be in the form of “this.funcName = function() {…};”. This makes these functions available as properties of the object created with new. If we were to do this:

[php lang=”JavaScript”]function Foo() {
function bar() {
alert(“hello world”);
}
}

var foo = new Foo();
foo.bar();[/php]

Nothing would happen. In fact, we’d get an error that “Object #< Foo > has no method ‘bar'”. However, this does work:

[php lang=”JavaScript”]function Foo() {
this.bar = function() {
alert(“hello world”);
}
}

var foo = new Foo();
foo.bar();[/php]

So in WireLibJS, after the variables are declared, all the functions are defined with “this”. Finally, the last block of code gets the width, height, context, etc.

[php lang=”JavaScript”] if (canvas !== undefined) {
width = canvas.width;
height = canvas.height;
cx = width / 2;
cy = height / 2;
cz = fl * 2;
context = canvas.getContext(“2d”);
}
[/php]

Note that nothing is returned explicitly. When using a constructor function, it automatically returns this. Another change I made is that there were a few “public” variables returned before: strokeStyle, lineWidth, showCenter, and clearCanvas. I could have continued to have them be properties like that by saying, for example, this.strokeStyle = “#000000”; somewhere in the function. However, I chose to create them as local vars and gave setters and getters to each of them: setStrokeStyle, getClearCanvas, etc. I’m not sure this was the “best” thing to do. I was just experimenting with different styles and this is where it landed as of this writing.

OK, then, let’s see it in action. First, we have a web page that includes this new file, and two canvases, a bit of css to position them, and another .js file:

[php lang=”html”]



wirelib



[/php]

and here’s our script:

[php lang=”JavaScript”]$(function() {
var wl1 = new Wirelib($(“#canvas1”)[0]), n = 0;
wl1.setStrokeStyle(“#ff0000”);
wl1.addBox(0, 0, 0, 100, 100, 100);
wl1.loop(24,
function() {
wl1.rotateY(0.1);
}
);
var wl2 = new Wirelib($(“#canvas2”)[0]);
wl2.setStrokeStyle(“#0000ff”);
wl2.addBox(0, 0, 0, 100, 100, 100);
wl2.loop(12,
function() {
wl2.rotateX(0.1);
wl2.rotateY(Math.sin(n) * 0.2);
n += 0.02;
}
);
});[/php]

And see it in action here.

Note that each object is in its own canvas, within its own div, rotates around its own center point, and has its own frame rate. Mission accomplished.

This entry was posted in JavaScript. Bookmark the permalink.

10 Responses to JavaScript Day 13: Rewriting WireLibJS

  1. creynders says:

    Although the approach you chose has the benefit of having privileged and private props and meths, it has a (huge) disadvantage:
    all method implementations reside on EACH instance of your WireLib class.

    If however you attach your methods to the prototype, this won’t be the case. All instances will share the same method implementations. This obviously has serious consequences concerning memory usage.

    I think it’s best (since you didn’t choose a Singleton implementation) to refactor and attach methods to the prototype instead of declaring them in the constructor. If I’m not mistaken of course! Something which – should that be the case – no doubt will be pointed out in the comments very rapidly… πŸ™‚

  2. keith says:

    Another thing I just realized I forgot to mention here is a change in the loop function. Previously, because there was only a single wirelib object, we could say, wirelib.draw() in there. Remember that the setInterval callback is run on the global scope, so “this” refers to the window object of the browser, not the object where the function was defined. So here, I’m creating a variable called wl and setting it to this outside of that callback function:

    var wl = this;

    That will refer to the current instance of Wirelib. Due to the magic of closures, that var will be carried into the callback function and be available, still pointing to the correct instance, when the callback is run by setInterval. So we can say:

    wl.draw();

    and have it work.

  3. Wirelib seems to work well enough even on my old iPhone 3G.
    You can see my spinning teaset here:

    http://www.crydust.be/lab/wirelib/

  4. GodsBoss says:

    Just a small clarification. You write “And you can use the β€œnew” keyword with any function to make a new object that will use that function as its prototype.” – The new object will not have the function as its prototype, but the prototype property of the function, in this case Wirelib.prototype.

    • keith says:

      To be fully accurate, the new object doesn’t actually have a prototype. The object’s __proto__ property will point to the function’s prototype. The object’s constructor will point to the function itself. This stuff never became second nature to me in AS1 and is still a bit confusing.

      • GodsBoss says:

        As of ECMA-262, 5th Edition (I think there is no difference to the 3rd Edition), objects have an internal [[Prototype]] property (it may not be implemented as an actual property, but if an implementation conforms to the specification, it must behave as it is one) and the description says “The prototype of this object.” See 8.6.2 “Object Internal Properties and Methods”, Table 8.

        If a function is used as a constructor (i.e. does use “new”) and does not return a value, an object is created and the value of the internal [[Prototype]] property is set to the same value as the prototype property of the function. So objects created via new Wirelib(canvas) do indeed have a prototype, it’s just not accessible. With at least one exception: __proto__ is a non-standard extension, it is kind of an interface to the internal [[Prototype]] property, so __proto__ === [[Prototype]].

  5. Arhtur Ogawa says:

    Another difference in the new 0.1 version’s loop() function is that it no longer uses the loopCallback var, present in the original. In the 0.1 code, the callback parameter to the loop() function must be in the closure for the anonymous function provided to setInterval.
    Yet another refinement to our understanding of closure, I guess.

Leave a Reply