UPDATE:
I took a closer look at your fiddle. A couple of things needed fixing. You're defining all methods inside the constructor. Just don't do that: each and every time you create an instance, you'll create the same functions over and over again. That's just total overkill. Here's a working fiddle, where all methods are prototype properties.
There was one line in particular that was causing some problems:
$(this).animate({width:200}, getRandomInt(500, 3000), engine.oncomplete);
Here, you're passing a reference to the oncomplete
method as a callback, but the context in which the function object is invoked is determined ad-hoc, so this wasn't referencing the instance engine
anymore, but that's an easy fix: just use a wrapper function:
$(this).animate({width:200}, getRandomInt(500, 3000), function()
{//this anonymous function will get some context
engine.oncomplete();//but invokes oncomplete in the context of engine, this references engine here
});
An alternative approach would be: a closure (like you, perhaps unknowingly, created with var self
), that overrides the prototype method:
engine.oncomplete = (function(that,prototypeMethod)
{//argument is reference to engine object, and the original method, that we're overriding
{
return function()
{//new version of the method, you can do this right after the instance is created
//or even in the constructor
return prototypeMethod.apply(that,[]);//call in correct context
};
}(engine,engine.oncomplete));
$(this).animate({width:200}, getRandomInt(500, 3000),engine.oncomplete);
delete engine.oncomplete;//reset to prototype method, optional
Either way, that's all it takes. Here's some more info on the matter - I think.
There are still a few things that can be tweaked a little, but on the whole, I think you're well on your way. Do remember: methods are best kept well clear of the constructor. if the second option is a bit vague (which it is, IMO).
I've promised myself that this will be the last link I'll add to this answer: I've asked a question a while back on memory-leaks in JavaScript, it's another one of my gargantuan posts, but just have a look at some of the code snippets: look at how the function reusableCallback
is invoked, also take a look at the "answer" I posted to my own question, there's an example of how native prototypes
can be used. And if ever you're bored, or if you're interested: try reading the code and work out what this
will reference at any given time. If you can work that out, I think it fair to say that you know the basic principles by which JS determines the scope.
You can't write this.finish
, simply because this
doesn't have a property called finish
. Just change:
Engine.finish = function(){};
to
Engine.prototype.finish = function(){};
Why? Simply because JS functions are first-class objects (they can be assigned to variables, well references to function objects anyways, they can be passed to functions as an argument, they can be the return value of a function, etc...)
Just try console.log(Engine instanceof Object);
, it'll log true. So what you're doing is this:
function Engine(){};
//is hoisted, and interpreted as:
var Engine = function(){};//<-- a function object, that is referenced by a var: Engine
Engine.finish = function(){};
//just like any object, assign a property to the object that Engine is referencing
But, when you create a new Engine object:
var myEngine = new Engine();
JS checks the prototype of whatever object Engine
is referencing, in this case: it references a function. The function is called using the new
keyword, so JS will create an object. For this, it needs prototypes (and lots of them). The prototype of this particular function (constructor) hasn't got any properties defined, so the JS looks for the next prototype in the chain, the Object
prototype. All methods and properties of that prototype are passed down to the new instance (try myEngine.toString();
and Object.prototype.toString === myEngine.toString
, you'll notice that they share the same method).
Note that this is not entirely true/accurate, and isn't even 10% of all the things that are going on when you're creating a new object from a constructor, but it may help you understand a few of the things to come:
Suppose you want every new instance to have a finish
method, and -as you know- all new Engine instances inherit methods that are in the Object.prototype
, you might thing of doing:
Object.prototype.finish = function(){};//!!
It'll work, sure enough, but so will this, then: someArray.finish
! Yes, arrays are objects, as are functions, references to DOM elements and what have you. So Don't change the Object prototype!
Instead JS allows you to define certain methods on a prototype level that is shared by all instances of that particular constructor:
function Engine(){};//emtpy
Engine.prototype.i = 0;
var newEngine = new Engine();
console.log(newEngine.i);//0
var anotherEngine = newEngine();
newEngine.i = 1234;
console.log(anotherEngine.i);//0
console.log(newEngine.i);//1234
This will create a basic prototype chain that looks like this:
EngineInstance {list of instance properties}
||
====>Engine.prototype {list of properties that all Engine instances have}
||
====> Object.prototype {properties for all objects, Engine is an object, so it should have these, too}
So whenever a new instance is created, that new instance is assigned whatever properties are defined in the prototypes. As an upshot, a method can be created once (rather than inside a constructor, which creates a new function for every instance), and all instances will share that method. but refer to MDN for more details
Just this one thing: JS accesses an objects properties in the same way an object is created, except that the order is reversed:
When invoking newEngine.finish
, JS will first look to see if that instance has its own finish
property, if not the Engine
prototype will be examined, if that doesn't have a finish method either, JS will carry on through the prototype chain until the desired property is either found (all the way up to the Object.prototype
), or, if event the Object prototype doesn't have the property, it'll just return undefined
, which says it all, really: this property is undefined
.
As an upshot of this, you can mask prototype methods/properties as long as you need them to act differently than their default behaviour, and after that simply delete
the modified method. Here's a real-life example:
function doAjax(data, url, method)
{
var xhr = makeAjaxInstance();
data.toString = function()
{
return JSON.stringify(this);
};
//some more stuff, but then:
xhr.send(data + '');//<-- add empty string forces implicit call to the toString method
delete data.toString;
}
doAjax('my/url',{foo:'bar'},'post');
JS does its usual routine: check instance for toString
method, and finds it. The function is called, and a neat JSON string is sent. After that, the toString
method is deleted.
JS searches for that property, on the instance itself. The method is deleted, so the toString method of the prototype is no longer masked, the next time it's called, JS will do the same trick, but instead of finding the method on data
, it'll scan the prototype and normal service is resumed.
I do hope this answer isn't too chaotic, but I'm Very tired, so if it is: I'm sorry