Wednesday, March 14, 2012

Javascript ASI and join vs concat(+)

Javascript Automatic Semicolon Insertion


I came across a nice implication of Automatic Semicolon Insertion while developing an API in javascript.

I'll let you guess at first as usual. Try the following
function asi() {
    var a = 10,
    b = 20
    c = 30;
    this.log = function () {
        console.log(a,b,c);
    };
    this.set = function (A,B,C) {
        a=A;
        b=B;
        c=C;        
    }
}

var a = new asi();
a.log();
var b = new asi();
b.log();
a.set(11,21,31);
b.log();
b.set('This', 'is', 'wrong');
a.log();

//Expected output
10 20 30
10 20 30
10 20 30
11 21 31

//What happened??
10 20 30
10 20 30
10 20 31
11 21 wrong

How Come?
First Thing to note:
See Closely at line 3 there is a comma operator missing. So, now parser will decide what to do :P
Remember:
Whenever a statement misses a semicolon and if the statement following it makes sense along with the former.
Then JS engine will not place a semicolon. Perhaps it parse them as a single statement. [Refer My Prev Post ]
Implication:
var a=10,b=20 remains a incomplete statement without a semicolon
var a=10,b=20c=30; doesn't makeout a valid javascript statement. So ASI makes it var a=10,b=20;c=30; [converse of the above]
Finally:
The variable c is assigned before declaration in the scope of function ASI
Remember:
If a variable is used before declaring it in function scope &&
If the variable is not declared anywhere in the scope chain of the function
Then it will become a property of the Global Scope or the window
Implication:
Hence, variable 'c' is assumed to be declared in the global scope rather in function ASI()

That is all to say about it :)
Better don't save semicolons :P
Use them whenever is needed.
So, that you will be able to trace back errors nicely in case of unintentional errors like the above.

Next is about Join Vs Concat(+)


I was going through many of the test regarding this context @jsperf
I inferred that in all modern browsers (+) for concatenation is optimized in a really nice way [in some cases (+) was 100 times better than join].

But I think the better criteria to choose one among them should be the usecase.
Because both of them will be able to do job in less than a ms

Following is an example why do I feel join is safer than (+)
function whyJoin() {
    var a, b, c, delim = '&';
    return {
        setter: function (A, B, C) {
            a = A;
            b = B;
            c = C;            
        },
        concatMe: function () {
            return a+delim+b+delim+c;
        },
        joinMe: function () {
            return [a, b, c].join(delim);
        }        
    };
}

var test = whyJoin();
test.setter('This', 'is', 'Good');
var c = test.concatMe();
var j = test.joinMe();
console.log(c.split('&')); 
console.log(j.split('&'));
test.setter('This', 'is');
c = test.concatMe();
console.log("Doesnt look good", c);
j = test.joinMe();
console.log("Seems Fine", j); 
console.log(c.split('&')); 
console.log(j.split('&'));
test.setter('This', 'is', null);
c = test.concatMe();
console.log("Doesnt look good", c);
j = test.joinMe();
console.log("Seems Fine", j); 
console.log(c.split('&')); 
console.log(j.split('&'));
If you had noticed your console following will be the output
["This", "is", "Good"]
["This", "is", "Good"]
Doesnt look good This&is&undefined
Seems Fine This&is&
["This", "is", "undefined"]
["This", "is", ""]
Doesnt look good This&is&null
Seems Fine This&is&
["This", "is", "null"]
["This", "is", ""]

Hope you noticed, In Concat(+) undefined or null is converted to their string equivalent and appended which might be undesired in some cases.

Also join keeps things clear & clean.
For ex: If delimiter is going to be the same across all concatenation or operands already exists as an array.

Concat(+) is really useful in many cases
For ex: If the concatenation is not based on some delimiters & number of concatenation operations is less
var result = '
  • '+param+'
  • ';
    In some cases I feel using both keep things clear.
    For ex:
    for(some condns) {
        result += [param, param, param].join('&');
    }
    

    But Google Optimization suggests creating a string builder for the above case.
    function stringBuilder() {
        this.needls = [];
    }
    stringBuilder.prototype.push = function (needle) {
        this.needls.push(needle);
    };
    stringBuilder.prototype.build = function () {
        var result = this.needls.join('');
        this.needls = [];
        return result;
    };
    var strBuilderInstance = new stringBuilder();
    for(some cdns) {
         strBuilderInstance.push([param, param, param].join('&'));
    }
    var result = strBuilderInstance.build();
    

    All the tests performed in jsperf are performance test :)
    Better decide things based on your usecase because javascript is fast enough but the DOM is taking all the time :) -> Douglas Crockford