Variables
In JavaScript, variable names (including function names) must be valid identifiers. The strict and complete rules for valid characters in identifiers are a little complex when you consider nontraditional characters such as Unicode. If you only consider typical ASCII alphanumeric characters though, the rules are simple.
An identifier must start with a
-z
, A
-Z
, $
, or _
. It can then contain any of those characters plus the numerals 0
-9
.
Generally, the same rules apply to a property name as to a variable identifier. However, certain words cannot be used as variables, but are OK as property names. These words are called “reserved words,” and include the JS keywords (for
, in
, if
, etc.) as well as null
, true
, and false
.
Note: For more information about reserved words, see Appendix A of the Types & Grammar title of this series.
Function Scopes
You use the var
keyword to declare a variable that will belong to the current function scope, or the global scope if at the top level outside of any function.
Hoisting
Wherever a var
appears inside a scope, that declaration is taken to belong to the entire scope and accessible everywhere throughout.
Metaphorically, this behavior is called hoisting, when a var
declaration is conceptually “moved” to the top of its enclosing scope. Technically, this process is more accurately explained by how code is compiled, but we can skip over those details for now.
Consider:
var a = 2;
foo(); // works because `foo()`
// declaration is "hoisted"
function foo() {
a = 3;
console.log( a ); // 3
var a; // declaration is "hoisted"
// to the top of `foo()`
}
console.log( a ); // 2
Warning: It’s not common or a good idea to rely on variable hoisting to use a variable earlier in its scope than its var
declaration appears; it can be quite confusing. It’s much more common and accepted to use hoisted function declarations, as we do with the foo()
call appearing before its formal declaration.
Nested Scopes
When you declare a variable, it is available anywhere in that scope, as well as any lower/inner scopes. For example:
function foo() {
var a = 1;
function bar() {
var b = 2;
function baz() {
var c = 3;
console.log( a, b, c ); // 1 2 3
}
baz();
console.log( a, b ); // 1 2
}
bar();
console.log( a ); // 1
}
foo();
Notice that c
is not available inside of bar()
, because it’s declared only inside the inner baz()
scope, and that b
is not available to foo()
for the same reason.
If you try to access a variable’s value in a scope where it’s not available, you’ll get a ReferenceError
thrown. If you try to set a variable that hasn’t been declared, you’ll either end up creating a variable in the top-level global scope (bad!) or getting an error, depending on “strict mode” (see “Strict Mode”). Let’s take a look:
function foo() {
a = 1; // `a` not formally declared
}
foo();
a; // 1 -- oops, auto global variable :(
This is a very bad practice. Don’t do it! Always formally declare your variables.
In addition to creating declarations for variables at the function level, ES6 lets you declare variables to belong to individual blocks (pairs of { .. }
), using the let
keyword. Besides some nuanced details, the scoping rules will behave roughly the same as we just saw with functions:
function foo() {
var a = 1;
if (a >= 1) {
let b = 2;
while (b < 5) {
let c = b * 2;
b++;
console.log( a + c );
}
}
}
foo();
// 5 7 9
Because of using let
instead of var
, b
will belong only to the if
statement and thus not to the whole foo()
function’s scope. Similarly, c
belongs only to the while
loop. Block scoping is very useful for managing your variable scopes in a more fine-grained fashion, which can make your code much easier to maintain over time.
Note: For more information about scope, see the Scope & Closures title of this series. See the ES6 & Beyond title of this series for more information about let
block scoping.