Scopes & Closures
Parminder Singh
-YDKJS series
Chapter 1: What's the Scope?
- To understand closely the underlying mechanisms used by the engine to organize and manage variables
- How does JS know which variables are accessible by any given statement
- How does it handle two variables of the same name?
-
Answer lies in the form of well-defined rules called "scope"
Intro
- JS is typically classified as an interpreted scripting language
- Its assumed that JS programs are processed in a single, top-down pass.
- But JS is in fact parsed/compiled in a separate phase before execution begins. Two phase execution.
Intro
let x = 20,
y = 10;
let result = add(x,y);
console.log(result);
function add(a, b){
return a + b;
}
- The code author's decisions on where to place variables, functions, and blocks with respect to each other are analyzed according to the rules of scope, during the initial parsing/compilation phase.
- The resulting scope structure is generally unaffected by runtime conditions.
- Functions hold and access variables, they maintain their original scope no matter where in the program the functions are eventually executed. This is called closure.
Intro
- Compilation is a set of steps that process the text of your code and turn it into a list of instructions the computer can understand.
- The whole source code is transformed at once, and those resulting instructions are saved as output (usually in a file) that can later be executed.
Compiled vs. Interpreted
- With interpretation the source code is transformed line by line
- Each line or statement is executed before immediately proceeding to processing the next line of the source code.
Compiled
Interpreted
Compiled vs. Interpreted
Compiled vs. Interpreted
Note: Modern JS engines actually employ numerous variations of both compilation and interpretation in the handling of JS programs.
Conclusion: JS is most accurately portrayed as a "Compiled" language
Code Compilation
Scope is primarily determined during compilation
Why does it even matter whether JS is compiled or not?
Code Compilation
Three stages of Code Compilation
- Tokenizing/Lexing - Breaking up a string of characters into meaningful chunks, called "tokens"
var a = 2; ➝ var, a, =, 2, ;
Lexing is a subtle variation of Tokenizing in the sense that it identifies tokens in a stateful way.
Code Compilation
Three stages of Code Compilation
- Parsing - Taking a stream (array) of tokens and turning it into a tree of nested elements viz AST(Abstract Syntax Tree)
Code Compilation
Three stages of Code Compilation
- Code Generation - taking an AST and turning it into executable code
⇨
Machine Instructions - to actually create a variable called a (including reserving memory, etc.), and then store a value into a.
JS Engine
- Vastly more complex than just these three stages
- Steps for performance optimizations involved
- Code can even be re-compiled and re-optimized during the progression of execution.
- No luxury of an abundance of time - no build step ahead of time unlike other languages.
- JS engines use all kinds of tricks (like JITs, which lazy compile and even hot re-compile)
JS Engine - Two Phases
Conclusion/Fact: Processing of JS programs occurs in (at least) two phases:
- Parsing/compilation
- Execution
Compile-then-execute approach
JS Engine - Two Phases - Proofs
Syntax Errors
var greeting = "Hello";
console.log(greeting);
greeting = ."Hi";
// SyntaxError: unexpected token .
JS engine first parses the entire program before any of it is executed
Syntax Errors
Syntax Errors
JS Engine - Two Phases - Proofs
Early Errors
console.log("Howdy");
saySomething("Hello","Hi");
// Uncaught SyntaxError: Duplicate parameter name not
// allowed in this context
function saySomething(greeting,greeting) {
"use strict";
console.log(greeting);
}
strict-mode forbids duplicate params
Error thrown is not syntax error but early error in strict mode
JS Engine - Two Phases - Proofs
Hoisting
function saySomething() {
var greeting = "Hello";
{
greeting = "Howdy"; // error comes from here
let greeting = "Hi";
console.log(greeting);
}
}
saySomething();
// ReferenceError: Cannot access 'greeting' before
// initialization
Accessing the greeting variable too early, a conflict referred to as the Temporal Dead Zone (TDZ)
JS Engine - Variables Identification
var students = [
{ id: 14, name: "Kyle" },
{ id: 73, name: "Suzy" },
{ id: 112, name: "Frank" },
{ id: 6, name: "Sarah" }
];
function getStudentName(studentID) {
for (let student of students) {
if (student.id == studentID) {
return student.name;
}
}
}
var nextStudent = getStudentName(73);
console.log(nextStudent);
// Suzy
Target vs Source value
var a= 2;
Targets - 5
Sources - 7
Cheating: Runtime Scope Modifications
function badIdea() {
eval("var oops = 'Ugh!';");
console.log(oops);
}
badIdea(); // Ugh!
In non-strict mode only.
eval compiles the string it has and executes it at runtime
Any vars or function declarations would modify its current scope of execution.
Performance Hit - Bad for already optimized code
var badIdea = { oops: "Ugh!" };
with (badIdea) {
console.log(oops); // Ugh!
}
Lexical Scope
- Refers to lexing stage of compilation
- Controlled entirely by the placement of functions, blocks, and variable declarations, in relation to one another.
- If you place a variable declaration inside a function, the compiler handles this declaration as it's parsing the function, and associates that declaration with the function's scope.
- If a variable is block-scope declared (let / const), then it's associated with the nearest enclosing { .. } block, rather than its enclosing function (as with var).
- Compilation creates a map of all the lexical scopes
Scopes & Closures - Chapter 1
By Param Singh
Scopes & Closures - Chapter 1
- 372