To get a job as a front end developer, we need to nail the coding interview.
In this article, we’ll look at some basic functions and scope questions.
What is Hoisting?
Hoisting means that a variable or function is moved to the top of their scope of where we defined the variable or function.
JavaScripts moves function declarations to the top of their scope of we can reference them later and gets all variable declarations and give them the value of undefined
.
During execution, the variables that were hoisted are assigned a value or runs functions.
Only function declarations and variables declared with the var
keyword are hoisted.
Variables declared with let
and const
constants aren’t hoisted. Also, arrow functions and function expressions aren’t hoisted.
For example, the following code has a function that’s hoisted:
foo();
function foo(){
console.log('foo')
}
foo
is hoisted, so it can be called before it’s defined since it’s a function declaration.
The following variable declaration is hoisted:
console.log(x);
var x = 1;
console.log(x);
The first console.log
outputs undefined
since var x
is hoisted.
Then when var x = 1
runs, x
is set to 1. Then we get 1 logged in the second console.log
since x
has its value set before it.
Anything else isn’t hoisted.
What is Scope?
JavaScript’s scope is the area where we have valid access to a variable or function.
There’re 3 kinds of scopes in JavaScript — global, function, and block scope.
Variables and functions that have global scope are accessible everywhere in the script or module file.
For example, we can declare a variable with global scope as follows:
var global = 'global';
const foo = () => {
console.log(global);
const bar = () => {
console.log(global);
}
bar();
}
foo();
Since we declared a variable with var
on top of the code, it’s accessible everywhere. So both console.log
s will output 'global'
.
Function scoped variables are only available inside a function.
We can define a function scoped variable as follows:
const foo = () => {
var fooString = 'foo';
console.log(fooString);
const bar = () => {
console.log(fooString);
}
bar();
}
foo();
console.log(fooString);
In the code above, we have the function scoped variable fooString
. It’s also declared with var
and it’s available inside both the foo
function and the nested bar
function.
Therefore, we’ll get ‘foo’
logged with the 2 console.log
statements inside the foo
function.
The console.log
at the bottom gives us an error because function scoped variables aren’t available outside a function.
Block scoped variables are only available inside a block. That is, inside an if
block, function block, loop, or an explicitly defined block. Anything delimited by curly braces is a block.
They’re defined either with let
or const
depending if it’s variable or constant.
For instance, if we write:
if (true) {
let x = 1;
console.log(x);
}
console.log(x);
Then x
is only available inside the if
block. The console.log
at the bottom gives us an x is not defined
error.
Likewise, if we have had a loop:
for (let i = 0; i <= 1; i++) {
let x = 1;
console.log(x);
}
console.log(x);
Then x
is only available inside the loop. The console.log
at the bottom gives us an x is not defined
error.
We can also define a block explicitly just to isolate variables from the outside:
{
let x = 1;
console.log(x);
}
console.log(x);
x
will then only be available inside the curly braces. The bottom console.log
will give us an error.
The scope determines how far the JavaScript will go to look for a variable. If it’s doesn’t exist in the current scope then it’ll look in the outer scope.
If it finds it in the outer scope and it’s declared in a way that we can access the variable then it’ll pick up that value.
Otherwise, if it’s not found, then we get an error.
What are Closures?
Closures are functions that remember the variables and parameters on its current scope and all the way up to the global scope.
In JavaScript, when an inner function has made available to any scope outside the outer function.
We can use it to expose private functions or data in a restricted way.
For example, we can write the following function that is a closure:
const foo = (() => {
let x = 'Joe';
const privateFn = () => {
alert(`hello ${x}`);
}
return {
publicFn() {
privateFn();
}
}
})();
foo.publicFn();
In the code above, we have an (Immediately Invoked Function Expression) IIFE that runs a function that returns an object with a publicFn
property, which is set to a function that calls privateFn
, which is hidden from the outside.
Also, privateFn
is called with x
declared inside the function.
Then we call publicFn
after assigning the returned object to foo
.
What the code achieves is that we hid the private items within a closure, while exposing what we want to expose to the outside.
As we can see, the closure lets us hold items that’s resides in a scope not available to the outside while we can expose what we can use outside the closure.
One major use of closures is to keep some entities private while exposing necessary functionality to the outside.
Conclusion
Hoisting is the pulling of functions and variables to the top of the code during compilation.
Function declarations are hoisted fully, and variables declared with var
has everything before the value assignment hoisted.
The scope is where a piece of code has valid access to a variable or constant.
Closures are a way to return inner functions with some of the entities of the outer function included. It’s useful for keeping some things private while exposing some functionality.