It’s been a while since I posted on my blog (busy traveling SEA). Today though, I’m taking the day to rest. This includes some light JavaScript reading (continuing the YDKJS series) and watching some Netflix. By the way, if you haven’t watched Westworld on HBO go do it now.
Anyway, back to the point of this blog. I’m reading about Scope & Closures today. The first chapter dives into LHS versus RHS and how the compiler interprets JavaScript. There are three main pieces to focus on. The engine that is responsible for compilation and execution, the compiler who helps parse and generate code, and scope which collects and maintains a look-up list of all declared identifiers. Scope is extremely important within JS, especially when trying to understand LHS versus RHS lookup. LHS is done when a variable appears on the left hand side of an assignment operation, while RHS look-up occurs when a variable appears on the right hand side of an assignment operation. This is a very simple explanation and doesn’t quite capture the full extent to how this works, but will suffice for now. Here are a couple examples.
LHS
var a = 2
RHS
function foo(a) {
Console.log(a);
}
foo(2);
Lexical scope is used in JS. Very quickly, scope can be thought of as bubbles. When a variable is declared in the outermost identifier, this is one bubble. Say within this identifier, a second is given. This would be bubble two. And say within this next identifier, a third is given. This would be bubble 3. Scope look-up (when the engine asks scope to identify a variable) always begins at the innermost level. So it starts at bubble 3 and works its way outward until it finds a match.
Do not cheat around lexical scope by using eval or with. Both of these cheat the otherwise author-times lexical scope by modifying or creating new lexical scope at runtime. This causes the engine trouble because it disregards an prior optimization. The lexing phase of compilation runs in knowing where and how identifiers are declared. If they are being changed at run time, you are disregarding this important part of how lexical scope performs efficiently.
Moving back to how bubbles are created, essentially a new function creates a new bubble (not entirely true, but ok for now). Wrapping code in a function arise from the software design principle, Principle of Least Privilege (Least Authority, Least Exposure). So when creating an API for a module / object, you should only expose what is necessary.
Functions as scope are important. There are two types, a function declaration and a function expressions. A function declaration occurs when function is the very first thing in the statement. Otherwise, it’s a function declaration. You can invoke function expressions immediately. For example:
var a = 2;
(function foo(){
var a = 3;
console.log(a);
})();
console.log(a);
Wrapping it in () and it is then invoked by using () after the last parens. So you no longer have to call the function before logging a to the console. These are called IIFE (immediately invoked function expression).
Anonymous functions are typically frowned upon because it makes debugging more difficult, self reference becomes difficult for recursive functions, and they omit s name which typically help make code more readable.
Although code blocking is standard in many other languages, it isn’t in JS. One way to implement blocks are to wrap code in {}
The let keyword (cousin to var) also allows declarations of variables in any arbitrary block of code.
These are especially useful when it comes to garbage collections and freeing up resources. It is not a replacement to functional scope, and both should be used to write maintainable and legible code.
Hoisting. Since the compiler looks for declarations first, they will be hoisted before executing the assignment. So var a is hoisted before executing a = 2. The same goes for functions. Functions that are declarative are hoisted, but function expressions are not. A subtle detail is that functions are hoisted first, then variables. It is important because duplicate definitions in the same scope are frowned upon and lead to confusing results.
Closure. In the words of the book, closures happen as a result of writing code the relies on lexical scope. They just happen. What is missing is your ability to recognize, embrace, and leverage them.
Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope. Example:
function foo() {
var a = 2;
function bar() {
console.log(a);
}
}
var baz = foo();
baz(); // 2 - closure is observed!
This happens because bar still has access to the outer functions variable even after it has returned. Closures have access to the outer function’s variable even after the outer function returns.
Another closure example:
function wait(message) {
setTimeout(function timer() {
console.log(message);
), 1000);
}
Wait("Hello, closure!");
Closure! You would expect the garbage collector to get rid of wait’s inner scope, but the inner timer function and its lexical scope reference are still intact. Hence, closure.
Modules. I’m having a bit of trouble understanding modules. Right now, they are another way to organize code that are enclosed in scope closure just like function-closure modules. It also seems like they are created in separate files that are then exported (thus made public) and then imported by another module of your choice. I’m going to do some more reading up on modules from this website.
As defined in YDKJS, modules require two key characteristics: 1. An outer wrapping function being invoked, to create the enclosing scope 2. The return value of the wrapping function must include reference to at least one inner function that has closure over the private inner scope of the wrapper.
Dynamic scope (in contrast to JavaScripts lexical scope) doesn’t concern itself with how and where functions / scopes are declared, but rather where they are called from. So the scope chain is based on the call-stack and not the nesting of scopes. The closest thing to dynamic scope in JS is the this mechanism, which has its own book. Gonna do some more reading on dynamic scope versus lexical scope. I found a great explanation on Wikipedia:
A fundamental distinction in scoping is what “part of a program” means. In languages with lexical scope (also called static scope), name resolution depends on the location in the source code and the lexical context, which is defined by where the named variable or function is defined. In contrast, in languages with dynamic scope the name resolution depends upon the program state when the name is encountered which is determined by the execution context or calling context. In practice, with lexical scope a variable’s definition is resolved by searching its containing block or function, then if that fails searching the outer containing block, and so on, whereas with dynamic scope the calling function is searched, then the function which called that calling function, and so on, progressing up the call stack. Of course, in both rules, we first look for a local definition of a variable.
Apologies for any formatting issues above. Typing this on mobile is a huge pain. As a finally note, I’ll most likely reread these books after I truly am diving into JS. Meaning in front of my laptop on a daily basis. Cheers from Vietnam!