Programming - - By James Edwards

(More) Assignment in Conditions

This is a follow-up to the post I wrote a few months ago, Assignment inside a Condition, in which I described a technique for assigning and evaluating variables in the same expression, and demonstrated it’s use for retrieving and referencing DOM nodes.

Though quite a few of you felt that the syntax was too confusing to be worth using; that it just makes for code which is too difficult to read:

if(summary = document.getElementById("post-summary"))
{
    return summary.innerHTML;
}

I have a certain sympathy with that point of view; but not very much, because the end-result is expressions that are less code, usually faster*, and can save memory (the top three goals of optimization!)

Carry on regardless

So over the last few months I’ve been experimenting with this technique, in a fairly ad-hoc way, looking for situations where it might be applicable and useful. I came up with a couple of new use-cases that seemed to work quite nicely; but I also kept seeing a problem that ultimately couldn’t be ignored (which one previous reader did in fact point-out) — the problem is one of variable scope and the undue creation of globals.

Avoiding the creation of globals

In the code example above, the expression has created a variable called summary; but the variable declaration is not preceded by var, nor could it be in that context:

if(var summary = document.getElementById("post-summary"))

That would just throw an error over the invalid syntax.

But without the preceding var we’ve created a global variable, because of JavaScript’s implied globals behavior (ie. any variable that’s not explicitly declared with var is created as a global). If we’ve already declared the variable elsewhere, then it’s not a problem. Otherwise, we’ll have to declare it first; but if we have to do that separately, before the assignment condition, then we’ve undermined some of the shortcut-value of the syntax!

So what I did in the end was simply declare a bunch of variables in advance, which can then be used anywhere, safe in the knowledge that their scope is constrained. It doesn’t really matter where you do it, as long as they’re encapsulated and available where you’ll need them — if you’re working inside a class or module pattern, then the top of its closure would seem like an obvious place. I ended up using the same variable names over and over again, wherever I needed temporary vars in order to use this syntax; and by re-using them, rather than creating a new one each time, I was ultimately able to save more memory.

Brackets enclosing assignment

As we noted last time, assignment returns a value, and if the assignment is wrapped in parentheses, then it’s enclosed as a single statement. It’s evaluated first, like in mathematical equations, and so we can use it exactly as though it contained the literal value.

This gives us new possibilities — to assign a value, and then perform an arbitrary evaluation on that value:

if((foo = bar) > 0)
{
    //foo is greater than zero
}

The variable bar in that example could equally be the return from a function, and saving it allows us to work with that value again, inside the condition:

if((foo = getFooValue()) > 0)
{
    alert(foo + " is greater than zero");
}

This is particularly useful as a way of saving shortcuts to long property references, which ultimately make the code run faster, as less object-traversal is necessary:

if((foo = this.prop.subprop.childprop.value) > 0)
{
    alert(foo + " is greater than zero");
}

Saving the execution of a regular-expression

Using this syntax, we can — execute a search for a regex pattern within a string, save the array of matches, and evaluate the result — all in the same expression (nb. exec is a method of RegExp, and returns either the matches array, or null if there were no matches):

if(matches = /^([a-z]+)[:](.*)$/.exec(data)) 
{ 
    alert("The key is " + matches[1] + " and the value is " + matches[2]); 
}

In that example, we’re relying on the fact that null evaluates to false and the matches array to true, and that’s a safe assumption in this case. But all the same, we don’t have to do that — we could wrap all of that in parentheses and then do a strict evaluation:

if((matches = /^([a-z]+)[:](.*)$/.exec(data)) !== null) 
{ 
    alert("There are " + matches.length + " matches"); 
}
else
{
    alert("There are no matches");
}

Carry on — don’t lose your head!

I’ll be the first to admit that this syntax looks a little odd, and thereby carries the potential for confusion and misunderstanding. But then, that’s true for countless things (at first) and not a reason for discarding them; like anything else — with time, it becomes familiar. And what it has to offer, to my mind, far outweighs what it costs:

  • What it offers is optimization — a tangible concern that affects all our users
  • What it costs is some readability — an abstract concern that only affects us

I don’t know about you, but I’d call that a bargain!

Thumbnail credit: sbwoodside

Sponsors