2

I was searching up how to fade an element with JavaScript earlier and I came across this function (object). I began wondering how does it work?

var fadeEffect=function(){
return{
    init:function(id, flag, target){
        this.elem = document.getElementById(id);
        clearInterval(this.elem.si);
        this.target = target ? target : flag ? 100 : 0;
        this.flag = flag || -1;
        this.alpha = this.elem.style.opacity ? parseFloat(this.elem.style.opacity) * 100 : 0;
        this.si = setInterval(function(){fadeEffect.tween()}, 20);
    },
    tween:function(){
        if(this.alpha == this.target){
            clearInterval(this.elem.si);
        }else{
            var value = Math.round(this.alpha + ((this.target - this.alpha) * .05)) + (1 * this.flag);
            this.elem.style.opacity = value / 100;
            this.elem.style.filter = 'alpha(opacity=' + value + ')';
            this.alpha = value
        }
    }
}
}();

I know that this is self invoking and only returns one object with two methods. My main concern this why does it use the this keyword? I am assuming the 'this' keyword is a placeholder for the object name "fadeEffect". I would understand if 'this' was used to create multiple objects... but why is it used here?

One other thing bothering me is this ternary operator...

   this.target = target ? target : flag ? 100 : 0;

How the heck does that work? It's like two ternary operators combined into one which I never thought was possible?

3 Answers 3

4

As for your second question. This will probably make it clearer:

this.target = (target ? target : (flag ? 100 : 0));

So yes, a nested ternary operator! Written out in words:

this.target = (is target a truthy value? Then use target. If not, then use the result from the last part -> (is flag a truthy value? Use 100. Otherwise, use 0)).

Sign up to request clarification or add additional context in comments.

Comments

3

Think of it as a namespace. the this keyword refers back to the object literal that the self invoking function returns. This means that this.target is accessible in the global namespace (or whatever scope the fadeEffect was defined) as an object property: fadeEffect.target, but it doesn't interfere with other variables that may exist in the outer scope.

The two methods set new properties of the returned object, that's all there is to it. Personally I find this to be, well, bad code... a closure would have been the better choice in this example:

var fadeEffect=function(){
    var elem,target,flag,alpha,si;//make private
    return{
        init:function(id, flag, target){
            elem = document.getElementById(id);
            clearInterval(elem.si);
            target = target ? target : flag ? 100 : 0;
            flag = flag || -1;
            alpha = elem.style.opacity ? parseFloat(elem.style.opacity) * 100 :0;
            si = setInterval(function(){fadeEffect.tween()}, 20);
        },
        tween:function(){
            if(alpha == target){
                clearInterval(si);//this.elem.si doesn't add up, init defines it as this.si
            }else{
                var value = Math.round(alpha + ((target - alpha) * .05))+ (1 * flag);
                elem.style.opacity = value / 100;
                elem.style.filter = 'alpha(opacity=' + value + ')';
                alpha = value
            }
        }
    }
}();

This does the same thing, but other code cannot interfere with the values of the target, or mess up the interval etc... you're right to say that the this keyword isn't required in this case, but I think the person who wrote this was either unfamiliar with JS closures, or at least insecure about how they work. This code effectively simulates a singleton pattern, or at least treats the object literal as an instance of a class. My guess is, the author is familiar with classical OOP, but not with prototypal inheritance. Anyway, the above code is safer, and safer is better IMHO


On the matter of your nested ternary, I've checked the code below using JSLint, and it suggested an even shorter, yet clearer alternative: use the default operator, followed by a ternary:

 //JSLint recommends this
 target = argTarget || argFlag ? 100 : 0;
 //over nested ternary
 target = argTarget ? argTarget : argFlag ? 100 : 0;

Anyway, here's the same code, only not using the dangerous this constructs, but using a closure, one of JavaScripts amazingly powerful features BTW, worth taking a closer look at what you can do with them!

var fadeEffect=(function()
{
    var elem,target,flag,alpha,si;//make private
    //define private 'methods': functions will be available, but only to return object
    //tween shouldn't be callable, it's a callback for the interval, which is set in init
    function tween()
    {
        if(alpha === target)
        {
           clearInterval(si);//this.elem.si doesn't add up, init defines it as this.si
        }
        else
        {
            alpha = Math.round(alpha + ((target - alpha) * 0.05))+ (1 * flag);
            //don't know why 1*flag is needed here, suggest:
            //alpha = Math.round(alpha + ((target - alpha) * 0.05)) + (+flag); +flag coerces to numeric
            elem.style.opacity = alpha / 100;
            elem.style.filter = 'alpha(opacity=' + alpha + ')';
        }
    }
    return{
        init:function(id, argFlag, argTarget)//arguments !== closure scope
        {
            if (si !== undefined && si !== null)
            {
                clearInterval(si);
            }
            elem = document.getElementById(id);
            //JSLint recommends this:
            target = argTarget || argFlag ? 100 : 0;
            //over nested ternary
            target = argTarget ? argTarget : argFlag ? 100 : 0;
            flag = argFlag || -1;
            alpha = elem.style.opacity ? parseFloat(elem.style.opacity) * 100 :0;
            si = setInterval(tween, 20);//just a reference to the tween function will do
        }
    };
})();
fadeEffect.init('someId',1,50);//will set things in motion
fadeEffect.tween();//undefined
console.log(fadeEffect.target);
fadeEffect.target = document.getElementById('someOtherId');//no problem, but won't change the value of var target

This way, the tween method cannot be called but by the interval, and the element on which the object, and its methods/functions are working their magic can never be overridden by external operations, they are inherent to the object. This makes for a safer construction, what's more, you can only really mess up 1 method: override the .init method, and the object is rendered useless, but harmless. Compare that to your code, where you could mess up both methods, but leave the interval standing... that's bad news: the interval would end up looking for a callback function that could very well have been deleted, causing your code to fail miserably:

//asume your code using this.tween();
fadeEffect.init('id',1,123);
delete fadeEffect.tween;
//inside fadeEffect:
setInterval(function(){fadeEffect.tween()}, 20);
//should be written as:
setInterval(fadeEffect.tween,20);
  // === setInterval(undefined,20); === :-(

5 Comments

Yeah that was what I was thinking. this isn't required in this case but I read above that it is used on the fly to create variables (properties) in this namespace. this.elem.si is an error I think because it doesn't add up to me either. It was defined as this.si. I like your code way better than what the person who wrote this code used. I have an exposure to closures and namespace. I can't talk about those subjects comfortably yet.
You're right on the money, saying this isn't necessary. The code you posted creates an object literal but is written as a constructor function. That's just confusing and bad practice. If you know what variables you're going to need, use a closure. The way your object works now, you might accidentally change the target property, causing the interval to fade out on a different element all of a sudden, thus ending up with 2 half-faded elements. Using a closure protects you from this. I'll work this code out a bit further to a usable, safe version (this one still has some issues to work out)
@ted - :-) that's up to the OP to decide, of course. Looking at it now, I must conceive it is rather long (and bulky). I'm not terribly good at explaining this stuff, so I tend to be too verbose. I do feel, however, and without wanting to boast, that my answer is the only one here that addresses both elements of the OP's question.
Imho, it's pretty cool to see the kind of answer showing how the javascript code is supposed to be written; I taught a few useful things for myself; Thanks (:
No @ted this answer wasn't too good to be accepted. I love this answer. It was clear and concise. He even helped me out in the comments. I meant to accept this answer but forgot to. Thank you very you support Elias! :)
2

One more explanation for this.target = target ? target : flag ? 100 : 0;:

if(target){
  this.target = target;
}
else{  
  if(flag){
    this.target = 100;
  } else {
    this.target = 0;
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.