Welcome to Part 2 of this review of the Pluralsight and Front End Masters course Advanced JavaScript by Kyle Simpson.
Kyle Simpson is Head of Curriculum for MakerSquare and an evangelist of the open web. He lives in Austin, Texas, and is passionate about all things JavaScript.
He’s written 8 books published by O’Reilly, including six books in the You Don’t Know JS series.
Kyle teaches JavaScript and has eight courses recorded by Front End Masters.
He’s a public speaker, and contributes to the world of OSS.
In this series:
Part 1 – Introduction
Part 2 – Scope
Part 3 – Lexical Scope
Part 4 – Block scoping
Part 5 – Dynamic Scope, Hoisting and this
Part 6 – Closure
Part 7 – Object Prototypes
Part 8 – Inheritance and OLOO
Part 9 – Async Patterns
Scope
To get the most out of this article, I recommend reading it in conjunction with Kyle’s book You Don’t Know JS: Scope & Closures. I include links to the appropriate sections throughout.
Scope and the JavaScript Compiler
Kyle says he’s going to push our comfort zone a little bit right off the bat, with compiler terminology.
Despite common belief to the contrary, JavaScript IS a compiled language! It is not compiled in the same way as languages such as C++ or Java however.
The most obvious difference is with JavaScript we distribute the original source code (or minified source code), not the binary form of the code.
Instead JavaScript code is compiled every time that it is run.
Kyle contrasts JavaScript with Bash scripting, which is a truly interpreted language. With bash when a particular line is run, it doesn’t know anything about the following line of code. It simply runs from top to bottom.
JavaScript and other compiled languages are more complex than that, doing an initial pass through the code to compile it, followed by at least one more pass and finally a pass to execute the code.
As of ES5.1 the smallest unit of scope in JavaScript was the function.
There’s ten lines of JavaScript code that Kyle talks about for 20-30 minutes:
var foo = “bar”;
function bar() {
var foo = “baz”;
}function baz(foo) {
foo = “bam”;
bam = “yay”;
}
Now obviously this is simple piece of code, but it represents a good test of our understanding of the JavaScript language.
The first focus of examining this code is finding declarations of variables and functions. Let’s start at line 1:
var foo = “bar”;
A variable declaration. Gramatically speaking this is a single statement, but JavaScript processes this as two separate operations:
- variable declaration: var foo
- initialization operation: foo = “bar”
These processes happen and different times and are performed by different mechanisms within the JS engine.
When the JS engine finds this variable declaration, it is in the global scope. It registers the foo into the global scope.
Then it moves onto line 3 where it finds a function declaration with the identifier bar. It registers the function bar into the global scope.
function bar() {
var foo = “baz”;
}
Compiler discussion
Kyle gives some more background on how modern compilers work, specifically JIT compilers, which do Just in time compilation.
In this example a JIT compiler will not compile the contents of the function bar yet. It skips over it so that it can come back and compile it when that work is strictly required to be performed.
More sophisticated still is hot-swapping the compilation of a function.
Due to the dynamic nature of JavaScript, JS engines need to make guesses in places where a C++ compiler knows exactly how a function is going to work.
Modern JavaScript engines make a guess and then monitor how well the guess is performing. If the guess was a bad one, it will throw away the compilation of the function and hot-swap in those bits directly.
In case you didn’t know already, compilers are very complex pieces of software.
For the sake of being about to reason about our code in a clear way, we will be thinking through how a naive, top-down compiler works with this JavaScript.
So in our example, the function bar is compiled now.
Compiling Function Scope
function bar() {
var foo = “baz”;
}
(Also see Understanding Scope)
The compiler finds the variable declaration of foo. The scope is not global anymore – we’re in the scope of bar. We can imagine a conversation between the compiler and the scope manager part of the engine.
There are no more variables to declare in this function. The next function is called baz:
function baz(foo) {
foo = “bam”;
bam = “yay”;
}
“Hey global scope, I have a declaration for an identifier named baz. It’s a function declaration. Add it to your declarations list for the global scope.”
Next we have an argument of foo.
“Hey baz scope, I have a declaration for an identifier named foo. I need your to register him there.”
Note that there is no var keyword present in this function. And we are not in strict mode.
For the purposes of scope resolution we are now done compiling our program!
A couple of microseconds later…the execution phase begins
[global] foo = “bar”;
function bar() {
[bar] foo = “baz”;
}function baz([baz] foo) {
foo = “bam”;
bam = “yay”;
}
The above block is pseudo-code attempting to represent the current state of compilation slightly better than the original code. It is not JavaScript code. The square brackets represent scope (not arrays). This is just my own sketch and I am not any sort of compiler expert.
Note that there is no var anymore on line 1.
Kyle now introduces some new terminology (and this come directly from the spec)
LHS – Left hand side of an assignment
RHS – Right hand side of an assignment
On line 1 foo is the LHS, and “bar” is the RHS.
Kyle says assignments can occur without an equals sign. For example when we pass in a variable to a function call.
So another way to define them is:
LHS – the target
RHS – the source
So if there was a conversation it would be something like
JS execution engine: “Hey global scope, I have an LHS reference for a variable named foo.”
Scope manager: “Yes, I know about foo.”
We don’t need to do any lookups for the RHS because we have an immediate value of “baz”. So we just take the value and copy it into that location.
And we have finished executing line one!
Execution of function code
Local and Global scope is discussed in further detail in the Nested Scope section of Scope and Closures chapter 1.
Next we execute in the scope of bar:
function bar() {
[bar] foo = “baz”;
}
“Hey bar scope (local scope), I have an LHS reference for a variable named foo. Do you know about it?”
“Yes, I know about foo”
Kyle says for the purposes of our discussion we don’t see baz being executed. We skip to the next line
function baz(foo) {
foo = “bam”;
bam = “yay”;
}
“Hey baz scope (local scope), I have an LHS reference for a variable named foo. Do you know about it?”
“Yes, it’s in my definition because I had a parameter named foo.”
Kyle says the next line of code is where people start to get pissed off with JavaScript.
“Hey baz scope (local scope), I have an LHS reference for a variable named bam. Do you know about it?”
“Never heard of bam”
“Hey global scope, I have an LHS reference for a variable named bam. Do you know about it?”
“Yes I just helpfully created him for you”
This is how we get leakage of global variables in JavaScript.
If we were in strict mode the answer would have been:
“Nope, never heard of bam”
(The most important takeaway for everyday coding here is to always use strict mode. See the Behaviors module of Jonathan Mills’ JavaScript Best Practices to learn many other situations where strict mode “saves your bacon”.)
Kyle gives the definition of an undeclared variable:
“we are unable to find a proper LHS reference for it in any of the scopes that we have access to”
It is important to understand that undeclared and undefined mean very different things.
Undefined means
“He was declared, but he has this special empty value that we mistakenly called undefined”
Kyle suggests uninitialized is a better word than undefined, because undefined is a value, not the absence of a value.
There are some questions from the audience next.
The first time I watched this course it kind of blew my mind how much there was going on it 10 lines of code that initially appear few simple. One year later, this doesn’t seem so hard anymore.
So if you found some of this hard to follow my advice is not to worry too much. However it is well worth taking your time to understand these fundamentals fully before moving on.
The pace of the course picks up from here on.
Scope and Execution Example
Now we look at a similar but more interesting variant of foo, bar, baz program:
var foo = “bar”;
function bar() {
var foo = “baz”;
function baz(foo) {
foo = “bam”;
bam = “yay”;
}
baz();
}bar();
foo; //???
bam; //???
baz(); //???
Kyle walks through the compilation and execution process again with the workshop audience.
To avoid repetition I will skip the initial compilation here (for the details watch the course).
However in order to improve your understanding of JavaScript this is an exercise that you should go through yourself using the terminology you’ve learnt.
Without actually running the program, what is the result of the execution of foo, bam and baz() at the end of this program?
The first slight surprise is the use of an RHS reference when executing line 13.
bar();
“Global scope, I have a RHS reference for a variable called bar.”
“Yes I know about it. Here’s a reference to that variable”
Now we try to execute the function object we get back.
function bar() {
var foo = “baz”;
function baz(foo) {
foo = “bam”;
bam = “yay”;
}
baz();
}
“Hey bar scope, I have an LHS reference for a variable called foo”
“Yes, I’ve heard of him, here’s the reference to the bar scope of foo”
Kyle describes the nested scope lookup process as “first-come first serve”. Also look at the tall building metaphor.
The next line to be executed is:
baz();
“Hey scope of bar, I have an RHS reference called baz. Have you heard of him?”
“Yes, here you go it’s a function”
Now we execute the function:
function baz(foo) {
foo = “bam”;
bam = “yay”;
}
“Hey scope of baz, I have a LHS reference for a variable called foo. Do you know it?”
“Yes”
The course shows the complete 16 line code reference at all times. If you’re reading this without the aid of the video, you’ll need to scroll up and down to understand the different scopes that are in play.
The foo that is assigned the value “bam” has the local scope baz not the outer scope bar. On the next line:
“Hey scope of baz, I have a LHS reference for a variable called bam. Do you know it?”
“Nope”
“Hey scope of bar, I have a LHS reference for a variable called bam. Do you know it?”
“Nope”
“Hey global scope, I have a LHS reference for a variable called bam. Do you know it?”
“I just made it for you. I’m such a helpful guy :-)”
Now we execute lines 14 to 16 of the code:
foo;
bam;
baz();
“Hey global scope, I have an RHS reference for a variable called foo, do you know him?”
“Yes, here you go.”
(The value of foo here is “bar”)
“Hey global scope, I have an RHS reference for a variable called bam, do you know him?”
“Yes, here you go.” (remember this was “helpfully” created in global scope and the value is “yay”)
“Hey global scope, I have an RHS reference for a variable called baz, do you know him?”
Now this is an interesting question. baz is a nested function inside bar. Can we access it’s value from the global scope?
Kyle says:
“in other cases where we ask the global scope for an LHS and it went unfulfilled, it was an undeclared declaration.
In non-strict mode, it automatically created one of us.
Within RHS reference that goes unfulfilled, that’s not automatically going to create a baz function…it’s going to…throw us what’s called a reference error.
…there is no identifier baz that’s in any scope that’s accessible to that current line of code.
I recommend having a read of the Errors section and Review section of Scope & Closures chapter 1 before continuing on.
Normally when I review Pluralsight courses, I write one post per Module.
For this course I think it is better to structure it to match the Scope & Closures book as much as possible.
The material that we’ve covered complements chapter 1. And we’ve already covered a lot of advanced concepts.
I strongly recommend investing the time to learn this at your own pace. Even if it takes you all day, or longer, to understand these concepts, it will be well worth it in the long run as it will make you a much better JavaScript developer.