I wouldn’t get too hung up on the first-class
terminology. Just think of all JavaScript functions as being strings that can be executed. Well they are much more than that, but this is a general description.
In reality, they are object instances of the Function class, which is also an object instance of the Object class (which everything traces back to in JavaScript).
Functions like all objects have properties. Every function has a this
and a return
object, and every function has its own scope. this
represents the execution context, and return
can reach across from callee scope (local) to caller scope (elsewhere).
From Function.prototype, functions inherit call()
and apply()
which are able to change the execution context, but that’s another discussion that’s probably over both of our heads at the moment. Something to learn about down the road.
JavaScript uses a two pass approach to load and parse script. On the first pass it identifies scope and hoists all declared objects to the top of their scope. Any variable declarations that do not use var
in the statement are hoisted to the top of global scope. Function declarations are hoisted in their entirety and are immutable. We cannot change them, or their name, and we cannot re-use the variable name deliberately or by accident.
Since declared functions are completely intact and loaded and ready on the first pass, it doesn’t matter where they are written in the source code. They can be referred from above or below.
Variables are hoisted a little differently. Only their identifier is hoisted, not the value that they refer to. On the second pass, JavaScript completes their definition as it comes across the assignments. Values are mutable, variable names are not.
Functions written as an anonymous expression are actually just a string assignment given to a variable. Like values, these are replaceable.
Consider the following:
function g() {flag = false;}
function h() {flag = true;}
var flag, f;
do {
f = flag ? g : h;
f();
console.log(flag);
} while (flag);
logs out,
true
false
We are able to toggle the function string on the fly while maintaining a single name for the function we invoke. This is very handy to be able to do.
Unlike declared functions, since expressions are not read until the second pass, these functions must be defined before any call to them can be made, so must appear above their first call in the source code.