[ /js/var ]

The mystery of Javascript Variables

As I wrote in a previous article, though javascript now boasts native execution, it was designed with the browser in mind. That means loading a program entails the real world cost of transmitting its source across a network.

Brevity is the soul of wit.

Many languages require you to declare that a variable is an integer or a float, signed or unsigned, public or private. Even if the variable's name is 'x', there are so many other keywords involved that you can't help but write verbose, often messy code. Stylistic guidelines such as limiting lines to 80 characters or less become unrealistic very quickly.

Javascript also exists in a niche where failure is to be avoided at all costs. It must run on many different browsers, and often it is more desirable for it to fail quietly than to crash your browser (or tab). Your console will list errors, but generally keep on going. If you fail to provide that single keyword needed to declare a variable, rather than telling you that you are wrong, it will guess at what you mean.

Sometimes it's right, sometimes it's wrong.

...Or so people think.

It's a language. It doesn't think. It was made by other programmers, but considerable discourse goes into deciding on such language features. Browsers are big business these days. These things are documented, and rather than get angry when the inference system behaves in an unexpected manner, it's up to you as a programer to understand what it's doing, why it's doing it, and how you can use that to write better (and shorter code).

'var' is variable declaration

Say you climb some proverbial mountain and declare your love for another person to the world. Love is all over the place, and there's a chance you've used the word before. In doing this, you are effectively saying that "this is the love that matters the most right now". It's the same in Javascript.

Remember that variable 'x' that you declared back in fourth grade? It was a good variable. Maybe to this day it's doing wonderful things with the memory allocated to it, but it's not the 'x' you mean when you say 'var x = 5;'.

Everything else is just variable access.

This means that if you aren't using a keyword to declare, you're just telling the interpreter to look up the value of 'x'. If 'x' hasn't been declared in your local scope, it looks 'up' to the parent scope.

If you're in a loop, it looks up to the containing function. If you're in a function, it looks up to whatever contains it. If you're in the global scope, though, there's nowhere left to go, and it's nice to have some default behaviour rather than just failing. Maybe you disagree, and think the people who decided on this were crazy.

Ok. Fine. Go use some other language.

Oh wait, what's that? You need Javascript? Then you'd better come to terms with this behaviour.

Oddness

/* oddly enough, this is a syntactically valid javascript program */

x=5 
// x is accessed at the global scope, where it was previously undefined
// we're performing an assignment operation
// javascript infers that we aren't looking for x, we want to create it
// there is now a variable 'x' at the global scope
// it is equal to 5

console.log(x)
// now we're on a new line, trying to invoke a function
// javascript *could* have been designed to fail here
// instead, its designers decided to give you a break
// it infers that console.log(x) is a distinct statement
// it references x, finds the value '5', and prints it

This is perfectly manageable in a small script that only uses the global scale, but if you programmed like this all the time, you probably wouldn't accomplish much. As mentioned earlier, we use the 'var' keyword to override another scope's possession of a keyword at the local scope.

x=5;
(function(){
  var x=7
  console.log(x)
})()
console.log(x);

In the snippet above, we:

  • create a global variable 'x'
  • enter an anonymous function's scope
    • declare a local variable 'x', and instantiate it with the value 7
    • write the value of the local 'x' to the console
  • exit the scope of the anonymous function and immediately invoke it
  • write the value of the variable 'x' (at the current (global) scope) to the console

This is the part where Javascript borrows from Lisp

I said that js just looks up, which is naturally a recursive definition.

If you nest another function inside of that first function, then you have another nested scope.

NOTE :: You may safely treat function argument names as though they were declared using the 'var' keyword.

var makeAdder = function(x){
  return function(y){
    return x+y;
  };
};
var x = makeAdder(2)(3);
console.log(x);

This is a closure. If you never stop to think about how the language behaves in a fundamental way, closures will likely always seem like magic.

Aside :: To be perfectly clear, closures aren't a js thing, they're a cs thing. Lots of languages behave in similar ways, at least in certain circumstances. As I understand it, Python behaves in this way when you write a nested function, but doesn't carry the behaviour through in a recursive way like Lisp or Javascript. I haven't touched Python in a long time, though, so I could be wrong.

The code above could have all been done in one line, but I stretched it out a bit to make it easier to understand.

  • makeAdder is a function which takes a single variable 'x', and returns a function
  • the returned function also takes a single variable, but internally references the value of makeAdder's argument
  • Thusly, the call makeAdder(2) returns a function which adds 2 to its argument
  • the entire call makeAdder(2)(3) passes the value 3 to that function, this value is assigned to the variable 'x'
  • x is printed to the console

I'm rehashing ideas from my previous article here, but it's a critical part of understanding variables.

Functions are special

The problem with learning Javascript piece by piece from blog posts is that people tend to overgeneralize. If you learn about closures using functions, you may be inclined to try utilizing this behaviour within a loop or an object.

Block statements

As explained here, variables declared in block/compound statements (as are commonly used in loops) reference their parent scope. Every complex statement that isn't a function (blocks, objects) behaves this way.As stated in that link: "block statements do not introduce a scope".

var x = 1;
{
  var x = 2;
}
console.log(x); // prints 2

Successive var statements in the same scope behave as normal reassignments. If you're coming from Lisp, you might expect this to throw an error or something. Javascript just keeps on going.

The real point here is that if you want to create a scope, you should use an anonymous function. You can do so anywhere, so there's no reason not to.

Consider this snippet:

var i="pew";
for(var i=0;i<5;i++){
  console.log(i); // 0,1,2,3,4
}
console.log(i); // 5

'i' is instantiated as a string: "pew". We enter a loop, assign the value 0 to 'i', and print the value of 'i' at each iteration of the loop until it completes. We print the value of 'i'. If loops had their own scope, this value would still be "pew", but they don't. As such, the final value is the number 5. Always keep in mind when writing javascript that unless they are encapsulated in their own scope, loops are destructive.

A block statement is a syntactic way of indicating that a sequence of statements are to be treated as one large statement. You can't assign a block statement to a variable, or pass it as an anonymous value in a situation where you could use a variable. If you want that kind of behaviour, you have to wrap it in a function.

Objects

var x = 5;
var myObj = {
  x:7
  ,y:(function(){console.log(x);return 8;})() // prints 5
  ,z:9
}
console.log(myObj); // prints the object

This object features an example of immediate function invocation at the time of its creation. It does not bind the function itself to the key 'y', though you could do so if you wanted to. Instead, it assigns the result of the function call.

The 'x' property of the object is not directly accessible in any way within this function call. If you wanted to coerce the interpreter into that kind of behaviour, once again, you would do so with a function.

var x=5;
var myObj = function(){
  var x=7,y=8,z=9;
  console.log(x); //prints 5
  return {x:x,y:y,z:z};
}(); 
console.log(myObj); // prints { x: 7, y: 8, z: 9 }

Object keys

There are a couple ways you can add keys to objects, fortunately, they all exhibit pretty much the same behaviour after the fact.

You can declare the entire object in one pass, as above with 'myObj'. Keys can be declared in the same fashion as variables, without quotes. Javascript coerces such keys into strings.

var myObj = {
  a:5,
  b:6,
  c:7,
  1:"pew",
  2:"pow"
};
console.log(myObj); // prints { '1': 'pew', '2': 'pow', a: 5, b: 6, c: 7 }
console.log(myObj['a']); // prints 5
console.log(myObj['1']); // prints pew
console.log(myObj[1]); // also prints pew

Otherwise, you can add keys one by one. Using one of two syntaxes mentioned in my last js article.

var myObj = {};

myObj.pew = "5";
myObj.pow = "6";
myObj["puw"] = "7";
myObj["piw"] = "8";

Conclusion

Javascript does indeed have a bunch of quirks. Lacking the requisite perspective over the whole language, this may make it seem poorly thought out. Armed with an overview of where to expect these quirks, however, you can use them to your advantage to write terse, highly expressive code.

Avoid trying to push the language to do what you think it should be doing, and use each aspect of the language to your advantage. There actually aren't that many rules.

If you want to make absolutely sure that code will not have unwanted side effects, give it its own scope by enclosing it in a function, using immediate invocation where appropriate. Not only does this keep you from overwriting another variable, if you later decide that you want to reuse some functionality, it's already in a modular form. Beyond this, you can pass these functions around and curry them, as I explain in my article on lambdas.

Avoid loops and code blocks wherever possible, they aren't modular, and there's a good chance it won't behave like you think it will, unless you have a clear concept of where things are being stored.

Try you best to end expressions with semicolons. Yes, Javascript's inferences often result in useful shorthands, but there are edge cases you probably won't think of. You may know what you're doing (now, at least), but others reading and modifying your code later on may not, and this may be the fastest way for a bug to creep in.

Finally, feel free to use the var keyword liberally. It will only overwrite variables in the current scope, which you probably want to do anyway. It won't throw any errors or halt execution, and if it is the first declaration in a scope, it will ensure that your function isn't fighting against the global scope.

My next article will elaborate on stylistic concerns, and how to write JS that fails fast, so you catch your bugs before moving on.

--ansuz

2014/10/11