Scope, hoisting, and closures tend to be a pretty tough topic in Javascript. Here are some notes I created to help me (and you) as a reference in the future. As always if you have questions or comments drop'em in the comments section and there is a handy reword feature to this site - just highlight any errors you see and drop a comment.
Scope
Scope is a tough subject but I'm going to try to break it down to the best of my understanding
Global Scopes
var person = 'Drew'
console.log(person);
window.person
window.person === person
If you run the lines above individually in a console log you will see that window.person and person are the same. Global variables are able to be called from the window
object as a method much like log
in console.log
Function Scopes
In function scoped values, you cannot call them outside of their block. The block referring to the { }
. What do you think will happen in the following scenario with console.log(age)
and console.log(hair)
?
const age = 100;
function go() {
const hair = 'blonde';
}
go();
console.log(age);
console.log(hair)
You guessed it, since age
is in the global scope, this will work but hair
but what happens when you try this:
const age = 100; function go() { const hair = 'blonde'; console.log(hair) } go(); console.log(age);
hair
will work because it's being called within the same block ( function (){ }
scope). You cannot call the variable outside of that function however you can call globally scoped values inside the function without issue.
var
variables are function scoped but let
and const
variables are block scoped. Let's take a look at this in practice. What do you think will happen when you run go();
?
const dog = 'snickers';
function logDog(){
console.log(dog);
};
function go(){
const dog = 'sunny';
logDog();
};
You guessed it, snickers - which is kind of counter-intuitive because you'd assume it'd be sunny since logDog() is inside the go() function but javascript follows a convention called lexical scoping
- it doesn't care about where it's called, but where it's defined. What happens when we pass a parameter intologdog()
and run go();
?
?const dog = 'snickers'; function logDog(){ console.log(dog); }; function go(){ const dog = 'sunny'; logDog('tank'); };
You guessed it, tank.
Try not to create global variables to avoid a lot of these confusions.
Hoisting
This allows you to call functions before they are declared in Javascript. Let's look at an example.
CalledBeforeItsDefined(); function CalledBeforeItsDefined() { console.log('Hoist me up!'); };
In the example above if you run it, the following will happen automagically (although your IDE may not like it).
All the functions in Javascript are "hoisted" up to the top of the javascript file at runtime.
Closures
Big scary closures! Let's unmask these closures
. A closure is the ability to access a parent function from the child function even when the parent function no longer exists. Below is not a closure but you can see the inner
function can call the outterVar
function outer () {
const outerVar = 'Hey Iam the outer Var!';
function inner () {
const innerVar = 'Hey I am an inner var!';
console.log(innerVar);
console.log(outerVar);
}
inner();
}
outer();
What happens if we call outer at a later time?
function outer () { const outerVar = 'Hey Iam the outer Var!'; function inner () { const innerVar = 'Hey I am an inner var!'; console.log(innerVar); console.log(outerVar); } return inner; } const innerFn = outer(); innerFN();
Will the outerVar
be accessible or undefined?
They both work actually! Where a closure comes into play - even though the outer()
function is done running, it will still maintain that function in memory so you can run it again. It's not garbage collected.
Why would this be helpful? Here is a function closure with a better real world example:
function createGreeting(greeting - ''){ const myGreet = greeting.toUpperCase(); return function (name) { return '${myGreet} ${name}`; } }
Why do this in two separate functions? Because you can make the greetings say other things by accessing the variables within the outer function again later.
function createGreeting(greeting - ''){ const myGreet = greeting.toUpperCase(); return function (name) { return '${myGreet} ${name}`; } } const sayHello = createGreeting('hello'); const sayHey = createGreeting('hey')
If we run console.log(sayhello('Drew');
or console.log(sayHey('Stranger')
the output will be HELLO Drew
and HEY Stranger
Because we created a variable (myGreet
) that is accessed at an inner scope - the closure - allows you to access the outer variable (name
) inside the inner variable after it's been closed. The key here is return
on the inner function and the outer function.
Recap: Closures are the ability for an inner function (inner scope) to access a higher scoped function after it's been called and run.
Drew is a seasoned DevOps Engineer with a rich background that spans multiple industries and technologies. With foundational training as a Nuclear Engineer in the US Navy, Drew brings a meticulous approach to operational efficiency and reliability. His expertise lies in cloud migration strategies, CI/CD automation, and Kubernetes orchestration. Known for a keen focus on facts and correctness, Drew is proficient in a range of programming languages including Bash and JavaScript. His diverse experiences, from serving in the military to working in the corporate world, have equipped him with a comprehensive worldview and a knack for creative problem-solving. Drew advocates for streamlined, fact-based approaches in both code and business, making him a reliable authority in the tech industry.