(This post is fueled by a reinvigorated interest in blogging on my site (as well as Medium) and the timeless sounds of David Bowie!)
After having an intensive meeting with the SEP, he shared his notes. One of the comments I had made regarding my code evaluation (on a previous project) was that I wanted to understand scope a bit more. You see, I had some places where it was plainly obvious that I needed some guidance.
After doing some intensive cramming late in the evening this week, here is what I learned from Chapter One: What is Scope?
So what is scope? Scope is a set of well-defined rules that store variables in a location that will let you find the variables later.
Compilation is comprised of three steps before the code is executed (random, but here’s a laugh I want to share with you, dear readers: I stared at my last typo for a reaaaaally long time. THE COD IS EXECUTED. Visions of fish in front of a firing squad distracted me for just a moment, but let’s get back to the point here).
The three steps of compilation are tokenizing/lexing, parsing, and code generation.
Tokenizing/lexing breaks up strings of characters into tokens or chunks. The crucial difference between tokenizing and lexing is whether or not tokens are identified as stateless or stateful.
When stateful parsing rules are invoked, the result will indicate whether the a in var a = 2; (for example) is considered distinct from or is part of another token (lexing).
Parsing is when you take a stream (array) of tokens and then turn it into a tree of nested elements. These elements represent the grammatical structure of the program. The tree is called an “AST” (which stands for Abstract Syntax Tree).
Going back to the var a = 2; example, the AST for this example could have a top-level node, a child node, another child node, and yet another child that has a child node. Still with me?
Code generation is the process of taking an AST and then turning it into executable code. This part varies greatly depending on the language, the platform it’s targeting, etc.
I have an immense appreciation for instruction that isn’t exclusively full of jargon, and so far, this book delivers! When Kyle treats scope regarding having a conversation with the characters engine, compiler, and scope, this is where his accessible teaching style shines. You’ll have to read this chapter to see what I mean!
The engine is responsible for start-to-finish compilation and execution. The compiler is the engine’s friend that handles parsing and code-generation. Engine has many friends, and their helpful friend scope will collect and maintain a list of variables. Scope may be their friend but he has an important role in their friendship as the enforcer. Someone has to adhere to strict rules regarding accessibility of variables.
Going back to the var a = 2; example, here’s how engine, compiler, and scope will approach it. The compiler will break it down to tokens via lexing. Then it will be parsed into a tree. Kyle breaks down what compiler will actually do.
Two distinct actions will be taken for variable assignments. If compiler doesn’t find a variable in the current scope, it will declare one. Then engine consults with scope and makes an assignment if it’s found.
The outcome of an assignment operation depends on the type of lookup that engine performs.
Engine performs an LHS (left-hand side) lookup for the variable a in our var a = 2; example. RHS (right-hand side) is the other type of lookup. Both are dependent on where the variable is located, whether it’s on the left or the right-hand side.
An LHS look-up tries to find the variable container itself so that it can make an assignment. However, anything that is NOT left-hand side is considered to be RHS, which looks up the value of a variable. Kyle uses the letters RHS to also mean “retrieve his/her source value.”
LHS and RHS don’t necessarily literally mean “left/right side of the assignment
= target operator.” Delineating between the target of the assignment (LHS) and who is the source of the assignment (RHS) is a better way to conceptualize this point.
Nested scope is a scope that’s nested inside another scope. If a variable isn’t found in the immediate scope, engine will consult the outer scope and continues if found or stops until the global scope is reached. In terms of thinking about levels, engine starts at the current scope and keeps going up one level (or more) until it reaches the outer scope and stops, whether or not it finds a variable.
LHS and RHS will behave differently depending on the variable, whether it’s declared or not. In the nested scopes, an RHS lookup will throw an error from engine if it doesn’t find a variable. ReferenceError is the particular type of error that is generated in this instance.
In an instance where an LHS look-up results in the engine arriving at the global scope without a variable (and the program isn’t in ‘strict mode’), the global scope creates a new variable of that name in the global scope. Engine will then be given that new variable.
Still with me? Me either. Half kidding. The material in this chapter alone is a lot to ponder and I will definitely be reviewing each chapter again and again.
A word about ‘strict mode’, and I never knew this before working with it. Strict mode was added in ES5 and one of its behaviors that differentiate it from regular mode is that it doesn’t allow the automatic (implicit) creation of a global variable. Engine would then throw a ReferenceError similarly to how it does in RHS.
A different type of error called TypeError will result if you do something impossible to its value. Executing a non-function value as a function is one example. Another example is if a property is referenced on a null or undefined value.
As I understand it, the difference between ReferenceError and TypeError is that ReferenceError is a failure pertaining to scope resolution. TypeError is thrown when an impossible action was performed against a successful scope resolution!
Whew. There you have it. Stay tuned for next week’s scintillating blog post on the next chapter of this book: You Don’t Know JS: Scope & Closures, chapter 2, where I write about what I learned about lexical scope.