Yesterday, I refactored WireLibJS to allow for multiple instances on multiple canvases. But, as correctly pointed out in the comments, there was something with the architecture of the way I did that, which could be considered to be a major flaw. Personally, I think on such a small library as WireLibJS, which is also only unlikely to have more than a couple of instances on any one page, it’s not likely to become a problem in any pragmatic sense. But it’s always good to explore different ways of doing things, best practices and optimization in any case, so let’s do it.
The problem with wirelib_0.1.js is that every instance creates all its functions newly every time a new instance is created. Each time you call new Wirelib(canvas), it goes through that function and creates brand new instances of each of those functions, attaching them to the new instance of the Wirelib object. If you create 10 instances of Wirelib, you get 10 separate instances of all those functions created, one for each new object. To demonstrate this, consider the following code:
[php lang=”JavaScript”]function Foo() {
this.bar = function() {
// nothing to do
}
}
var foo1 = new Foo();
var foo2 = new Foo();
alert(foo1.bar == foo2.bar); // false[/php]
Here we have a constructor function named Foo. We make two new instances called foo1 and foo2. As you create each of these, the constructor function executes, creating an anonymous function and assigning it to a property on the new instance, called bar. Now, each instance, foo1 and foo2, have a bar property which is a function, but a quick comparison shows that they are not the same function. They have the same name and the same code inside them, but each was created in a new memory location. This could get expensive in terms of memory and CPU if you have more than a few of these things being created.
The solution is to create the functions on the constructor function’s prototype. Every function has a property called prototype, which is very simply an object. There’s nothing very special about it. It doesn’t have any special properties or anything else. But the way it is used by JavaScript makes it very useful.
console.log(“prototype: ” + Foo.prototype); // prototype: [object Object]
When you create an instance of a constructor function, that instance has a property called constructor, which points back to the constructor function that was used to create it.
var foo1 = new Foo();
console.log(foo1.constructor); // traces out whole Foo function
So from foo1 you could get back to the prototype of its constructor by saying foo1.constructor.prototype. There’s another shortcut which points to the same thing: foo1.__proto__ – note that there are two underscores before and after the proto. Although this works in some cases, my understanding is that __proto__ is not an official part of JS and not supported by all versions of all browsers.
Now, when you try to access a property or method on foo1, it first looks on the instance itself. If it finds it on the instance, fine. If not, it finds the instance’s constructor and looks for that property or method on the prototype of the constructor. Now, if it doesn’t find the property there, it will look for the constructor of the prototype itself, and check its prototype, and so on up the line, known as walking the prototype chain. Flash devs, is this all coming back to you from AS1 days? Same basic setup. This behavior is what allows you to do pseudo-classes and inheritance, if you so choose. We won’t be doing any of that here though.
So, knowing this behavior, we can add functions to Foo’s prototype and they should work seamlessly on instances of Foo.
[php lang=”JavaScript”]function Foo() {
this.bar = function() {
// nothing to do
}
}
Foo.prototype.bar2 = function() {
// still doing nothing
}
var foo1 = new Foo();
var foo2 = new Foo();
alert(foo1.bar2 == foo2.bar2); // true[/php]
Now, since bar2 has been defined on Foo’s prototype, it is still available to both instances. Furthermore, it is only defined a single time, instead of once for each instance. We prove that by showing that bar2 is the same object on both instances.
With all that in mind, I refactored WireLibJS to use prototype. You can see it here:
http://www.bit-101.com/jscanvas/wirelib_0.2.js
This required some other major changes in the library. Earlier, all the functions were defined within the Wirelib constructor function, so had access to all its local variables. This is no longer the case, so all those local variables had to be redefined as instance variables. And anywhere they are referenced, they need to be referenced with “this”. The constructor function itself looks like this:
[php lang=”JavaScript”]function Wirelib(canvas) {
if (canvas !== undefined) {
this.lines = [];
this.fl = 250;
this.strokeStyle = “#000000”;
this.lineWidth = 1;
this.showCenter = false;
this.clearCanvas = true;
this.width = canvas.width;
this.height = canvas.height;
this.cx = this.width / 2;
this.cy = this.height / 2;
this.cz = this.fl * 2;
this.context = canvas.getContext(“2d”);
}
}[/php]
Note well that all the prototype function definitions are OUTSIDE of the constructor function. If you defined them inside, you would still be defining them on the prototype, but once again REdefining them for each instance, defeating the whole purpose.
Defining all previous local vars as instance vars also means that they are no longer “private” vars accessible from the member functions alone. All instance vars can be accessed directly from anything that has access to the instance. There are ways to address this as well, but I’m not going to worry about it for now. It also means that those accessor functions I created yesterday are now unnecessary, nor is the setCenter function, as you can now set cx, cy, and cz directly. So I removed those.
Here’s the example, though it doesn’t look any different than yesterdays: