This blog post is a summary of what I could understand from the first 2 chapters of this book.
Chapter 1: What's Scope?
Scope refers to a part of the program where variables/data declared in that part of the program are accessible to the program based on where they were declared. Confusing? Let's try one more time. A variable in any part of a program can be accessed by the program in other parts based on where the variable was declared/exists. This is where the term scope
comes in to simplify things and make our understanding better by actually having some rules that help to predict/understand and access variables/functions easier.
Compiled vs Interpreted
- What does compiled or interpreted mean? So computers don't understand human language. They work on 1's and 0's. So in the initial days, people wrote code in assembly language that was converted to machine code by processors. But it was hard you didn't get a lot of flexibility with those languages. On top of that, it was harder to understand and learn. That's where languages that were close to English started coming in ex. Fortran, COBOL.
- But we still had one issue ⇒ Computers don't understand that. Computers need instruction to complete tasks.
- That's where this process comes in that converts a program that's written in a high-level language to instructions that the computer would understand.
- This process has different models like compiled and interpreted. How do they differ? In code compilation, the compiler compiles the whole file in one pass and then generates a new file that could be used to run the program later. In the interpreted model, your program still compiles the code in machine instruction but instead of doing that at once, it does this line by line.
Is JS interpreted language?
- So most of the time, JS is considered an interpreted language but Kyle has written and gave proof that how JS isn't interpreted but compiled in the first book.
What does compiling code mean?
- Why does it even matter if JS is compiled or interpreted? Since we're discussing scope, the scope is defined during this phase only.
- What does happen during compilation? So basically there are 3 steps taken during compiling any code.
- Tokenizing/Lexing: This step refers to tagging reserved keywords of language. Breaking whole program into chunks that language understands.
- Parsing: Taking streams of tokens and turning them into a tree of several elements. This is called as
Abstract Syntax Tree(AST)
. - Code Generation: Converting the received AST to an executable code by machine.
Two Phases
- Compilation Phase
- Execution Phase
- Kyle said these 2 phases can be actually observed through JS programs and are not just facts in theory.
- We can observe this pattern if we look and notice the following things:
syntax error
,early errors
,hoisting
.
Compiler Speaks
- The following program has an array of addresses that has two property city and country. We see a function
getCountry
that receives a parameter named city and based on city name, it returns name of associated country inside object.
const addresses = [
{
city: "SF",
country: "US"
},
{
city: "MUM",
country: "IND"
},
]
function getCountry(city) {
for(let address of addresses) {
if (address.city === city) {
return address.country;
}
}
}
const country = getCountry("SF");
console.log(country);
- Every variable/identifier in any program act as one of these 2 roles:
target
andsource
.
What's the target/source? How does someone identify them?
- Variables that are being assigned a value are a target in our program and rest of the variable/identifiers are sources.
- In above code,
addresses
,city
parameter of getCountry function,address
in for loop block andcountry
are targets of this program. Since they were being assigned value. But we left one more target to mentions above. - That's
function getCountry(city)
. Yes, function declarations are subtle target reference that exists in JS codes.
Scope is defined during compilation phase, so you can't really change it during runtime. But JS also has ways do this through
-
eval(...)
function badIdea() { eval("var a = 2;"); } badIdea();
-
with()
const info = { name: "John", age: 30 }; with(info) { console.log(name); console.log(age); }
So following way, you could still change scope during runtime but Kyle suggested avoiding this at all costs and they're not available in strict mode anyways.
Chapter 2: Illustrating Lexical Scope
Let's have a look at the code snippet that we used in the last section.
const addresses = [
{
city: "SF",
country: "US"
},
{
city: "MUM",
country: "IND"
},
]
function getCountry(city) {
for(let address of addresses) {
if (address.city === city) {
return address.country;
}
}
}
const country = getCountry("SF");
console.log(country);
Now Kyle came up with the following metaphor/mental model to understand scope through
- Marbles, Bucket and Bubbles
Now I am gonna explain/write what I understood through his metaphor and explanations. For an accurate mental model, I suggest reading this section.
- In the code above, there are 3 scopes that we can observe. The outer scope ie Global Scope, the scope of the function, and since JS now blocks also create new scope, we can see the scope created by for loop.
-
Through Kyle's metaphor,
⇒ a scope is represented by a colored bubble
⇒ each scope has their own scope bucket
⇒ a variable/identifier represents marble and belongs to a scope bucket depending on the bubble it resides.
So we have 3 different scopes we would say
BUBBLE 1 - The outermost scope holding marbles addresses
, country
and getCountry
BUBBLE 2 - The scope of function getCountry holding marble city
BUBBLE 3 - The scope of for-loop holding marble address
How does scoping work in their bubble?
A scope can only access its outer scope and can not access scopes that are nested inside them.
⇒ So expressions inside BUBBLE1 can access
marbles of BUBBLE1, not
BUBBLE2, not
BUBBLE3.
⇒ An expression inside BUBBLE2 can access
marbles of BUBBLE2, can access
marbles of BUBBLE1, but not
BUBBLE3.
⇒ An expression inside BUBBLE3 can access
marbles of BUBBLE3, can access
marbles of BUBBLE2, and can access
marble of BUBBLE1.
A conversation among friends
In this section, Kyle wrote about how these variables are put into respective bubble and scope buckets during compilation and how does the lookup happen for the marbles aka variables/identifiers during code execution based on bubbles.
Whenever JS engines would start processing code, it would happen in 2 phases
- Compilation
- Code execution
what does happen in the compilation phase?
- compiler starts compiling the code and it takes help of scope manager to create declared variables
- compiler also asks scope manager to create functions declaration and create a separate scope for that function with its own scope bucket and scope manager ie function scope manager
- compiler does this for all of target references it finds and asks scope manager, maybe global scope manager or maybe functions' scope manager or maybe a new blocks' scope manager depending on where the control of the program is in right now, to create that space in memory
what does happen in the execution phase?
- Now in this phase engine asks respective scope managers if the target reference it found exists in scope from the compilation phase. If scope manager says, yes it exists in current scope then engine assigns undefined to it so that it's ready to use when program actually starts execution
- If the current scope manager doesn't have that target reference then it asks engine to go in outer scopes and ask the respective scope manager
Nested Scopes
We saw if the current scope doesn't have a variable/identifier then engine goes to next outer scope and asks respective scope manager. This stops once the engine reaches global scope. If engine doesn't find the variable in global scope as well then it results in an error. These errors are handled differently based on if they're target/source. Usually, a reference error is thrown.
accidental globals
In non-strict mode, when a program tries to assign a target reference a value and if value was never declared either in current scope or in next outer scope then scope manager(global) goes ahead and creates a new variable but this look should've resulted in failure.
function getStudentName() {
// assignment to an undeclared variable :(
nextStudent = "Suzy";
}
getStudentName();
console.log(nextStudent);
// "Suzy" -- oops, an accidental-global variable!
One more metaphor
Kyle came up with one more metaphor is an office building. So if you're trying to resolve a target/source reference so you first start by searching the first floor and when you don't find that you proceed to search on the next floor and you do this until you reach last floor in building and you have no more floor to go.
Top comments (0)