Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does this JavaScript function?

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?

like image 834
W3Geek Avatar asked Mar 26 '26 15:03

W3Geek


2 Answers

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)).

like image 115
Willem Mulder Avatar answered Mar 29 '26 04:03

Willem Mulder


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); === :-(
like image 34
Elias Van Ootegem Avatar answered Mar 29 '26 05:03

Elias Van Ootegem



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!