Function Scope and Hoisting
Function Scope
In JavaScript, defining a function creates a scope. The scope is lexical, or static, which means what you can access is decided by where the code is written, not by what happens at run time.
JavaScript has two kinds of scope:
- Global Scope: Variables, functions, and objects declared in the global scope can be used anywhere in the code.
- Local Scope: This covers function scopes and block scopes (block scope came with
letandconstin ES6).
A variable defined inside a function cannot be read from outside that function. That keeps the variable private to the function, and it means the name will not collide with a name in the global scope.
function myFunction() {
var num = 4;
}
console.log(num); // ReferenceError: num is not defined
Hoisting
Hoisting means that JavaScript processes function declarations during the compilation phase, before the code starts running. So we can call a function before the point where it appears in the script.
const result = add(2, 1);
console.log(result); // 3
function add(a, b) {
return a + b;
}
Function Declarations vs. Function Expressions
Function declarations are hoisted, so we can call them before they are defined. Function expressions do not work that way. When the function is assigned to a const or a let, the variable cannot be accessed before its declaration, so calling it too early gives a ReferenceError.
const result = add(2, 1);
console.log(result); // ReferenceError: Cannot access 'add' before initialization
const add = function (a, b) {
return a + b;
};
When Hoisting is Helpful
Hoisting makes some patterns possible, such as mutual recursion. Here is an example:
function isEven(n) {
return n == 0 ? true : isOdd(n - 1);
}
function isOdd(n) {
return n == 0 ? false : isEven(n - 1);
}
const n = 3;
console.log(`isEven(${n}) -> ${isEven(n)}`); // isEven(3) -> false
console.log(`isOdd(${n}) -> ${isOdd(n)}`); // isOdd(3) -> true
These two functions call each other. Because of hoisting, we can declare them in either order and the code still works.
When Hoisting Leads to Unexpected Results
Hoisting of variables inside a function can give a result you did not expect. Here is an example:
var num = 1;
print();
function print() {
console.log(num); // undefined
var num = 2;
}
The local variable num inside print is hoisted to the top of the function’s scope. It shadows the outer num, and it has no value yet at the point of the console.log, so the output is undefined.