2

I need a simple engine that tells me when some animations (but could also be ajax call or anything that has some kind of "onComplete" function) finsh, when all of the registered animation has finished i mean.

I wrote a simple jsFiddle.

And I have some doubt:

  1. Why I can't write this.i or this.finish but I have to use Engine.i or Engine.finish ? Is there a better way to write such a simple class? I have tried to document a bit here but it is still not clear. This inside Engine is a div because it is called inside a $("#somediv").bind('click', function() { }) but I don't know if the use of a static (??) variable on the object Engine is the right way to handle it. thanks to this answer i have resolved with "this duplication in self" (how can i call this method?)

  2. can jquery deferred object help me in this case? maybe there is a more simple and more effective way to write it with deferred object. I need something that i can invoke on anything (animation, on done on preloading, ajax)..

hope it is clear.. any advice or link to something that handle similar situations is appreciated!

last code

now the last code:

engine = new Engine(); 

$("#clickme").bind('click', function() {
    $(".bar").each(function() {
        engine.add()
        $(this).animate({width:200}, getRandomInt(500, 3000), 
                        engine.oncomplete);                      
    });
});

// animation engine
function Engine() {   
    var self = this;
    this.i = 0;
    this.add = function() {
        self.i++;
    }
    this.oncomplete = function() {
        self.i--;
        if(self.i<=0){
            self.finish();
        }
    }     
    this.finish = function() {
        alert("done!");           
    }
}

Community
  • 1
  • 1
nkint
  • 11,513
  • 31
  • 103
  • 174
  • Always post the relevant code and markup **in the question itself**, don't just link. http://meta.stackexchange.com/questions/118392/add-stack-overfow-faq-entry-or-similar-for-putting-code-in-the-question – T.J. Crowder Dec 05 '12 at 12:15
  • i thought it was more clear on jsfiddle, thanks – nkint Dec 05 '12 at 12:22
  • @ nkint: It's a both/and thing. :-) – T.J. Crowder Dec 05 '12 at 12:23
  • 1
    keeping a reference to `this` (the current context) in a variable, that is used by the return value of the function in which you assigned `this` to that var is a closure. The var is local to the function's scope, so it should be GC'ed after the function returns, but because the return value (in case of a constructor: the instance) does not go out of scope, and references the var, it is kept in memory, but can only be accessed by the object that was returned in the first place. Thus that variable is _closed off_ from the rest of the script, hence: _closure_ – Elias Van Ootegem Dec 05 '12 at 17:12

2 Answers2

1

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

Community
  • 1
  • 1
Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149
  • but the same as for class variable i.. anyway, how would you write something similar? or better, what is the best way to write this? – nkint Dec 05 '12 at 13:08
  • @nkint: JS doesn't have classes, but `prototypes`, so think of `prototypes` as classes. Just add a `.prototype` between `Engine` and `.finish`, that's all you need to do, really. I, however, rarely use constructors anymore, in favour of [the module pattern](http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth), I'd create a closure with my "prototype" methods, and return an object literal and return that, but expanding on closures would turn my already vast answer into a book – Elias Van Ootegem Dec 05 '12 at 13:29
  • very clear! thanks! anyway i have resolved with "var self = this;" pattern. anyway super usefull information in your post! thanks! – nkint Dec 05 '12 at 13:41
  • @nkint: `var self = this;`, hmm sounds like you're on your way to using the _module pattern_. That's always a good sign ;), welcome to the world of sanity and good practice. Though one tiny suggestion: `self` is a circular reference in Workers and, on some engines, the global object. Perhaps use `_self` or `that` to avoid confusion? – Elias Van Ootegem Dec 05 '12 at 14:29
  • @nkint: oh, and on the deferred object-thing: no, that's not the way forwards... even if you did manage to work it out that way (using custom events and what have you), it'll just slow the Engine object down, which is what you _must_ avoid when dealing with engines. If you do want to experiment with jQuery here, I think `$.when` is a better fit – Elias Van Ootegem Dec 05 '12 at 14:37
  • @nkint: I've had a closer look at your fiddle, and forked it. Added the link and (attempted to) clarify a couple of things about context, this reference and scope – Elias Van Ootegem Dec 05 '12 at 16:27
  • thank you very much. i wish every time meet some good answer like this! : ) – nkint Dec 05 '12 at 18:13
0

Addressing point 1: By using Engine.i and Engine.finish, you are defining static fields. You might want to use Engine.prototype.i and Engine.prototype.finish.

Community
  • 1
  • 1
  • He';s not defining statics, he's defining properties of the `Engine` function object, that happens to be a constructor. Functions are objects, just like any other object, and can have properties – Elias Van Ootegem Dec 05 '12 at 12:50