% js-scope-closures-this.tex — execution contexts, lexical environments, hoisting + TDZ,
% per-iteration let bindings, closures (what is captured, memory), the `this` rules in
% priority order, arrows, lost-this, bind permanence, strict mode.
% Source: own knowledge, aligned with ECMAScript (Environment Records,
% FunctionDeclarationInstantiation, CreatePerIterationEnvironment). Senior-interview level.
% Stale closures in React hooks: see react-native/react-hooks-deep.tex (not repeated here).
% Build ONLY with: tools/print/print-sheet.py <this>.tex --dry-run
% @source: hiot monorepo, docs/school/sheets/javascript/js-scope-closures-this.tex — the SOURCE OF TRUTH; a copy anywhere else (e.g. artur.gurgul.pro) is regenerated from it, never edited
% @labels: area=javascript kind=concept level=core platform=web new=no round=typescript-2026-09-24 topic=language
% @tags: closures, lexical-scope, hoisting, tdz, var-vs-let, execution-context, scope-chain, this-binding, arrow-functions, bind, call-apply, strict-mode
\documentclass[8pt]{extarticle}
\usepackage{printup-sheet}
\usepackage{array}

\lstdefinelanguage{TSSheet}{
  morekeywords={import,from,export,default,const,let,var,type,interface,extends,function,
    return,async,await,if,else,switch,case,new,true,false,null,undefined,typeof,keyof,
    as,satisfies,never,string,number,boolean,void,declare,namespace,readonly,class,this,
    for,of,in,static,super,yield,delete,throw,try,catch},
  sensitive=true, morecomment=[l]{//}, morecomment=[s]{/*}{*/},
  morestring=[b]", morestring=[b]', morestring=[b]`}

\tikzset{
  sb/.style={box, font=\scriptsize, inner sep=1.5pt, minimum height=4.2mm},
  lbl/.style={font=\tiny, text=black!75, inner sep=1pt, align=center},
  pt/.style={font=\bfseries\small, anchor=west},
  q/.style={draw=sheetGrey, rounded corners=1pt, font=\scriptsize, inner sep=1.5pt,
            minimum height=3.9mm, anchor=west, fill=black!3},
  a/.style={font=\scriptsize, anchor=west, inner sep=1pt},
}
\newcommand\ar{\ensuremath{\Rightarrow}}
\newcommand\fat{\hbox{=}\hbox{>}}% "=>" without the 0xProto ligature

\begin{document}

\sheettitle{Scope, closures and \texttt{this}}{javascript · memo}

\oneliner{Scope is \textbf{lexical} — fixed by where code is \emph{written}: every function
keeps a hidden \texttt{[[Environment]]} pointer to the environment it was created in, and a
closure is that pointer keeping \textbf{variables} (not values) alive. \texttt{this} is the
opposite — \textbf{dynamic}, decided by \emph{how the function is called} (except arrows).}

\vspace{2pt}
\noindent\begin{tikzpicture}[sheet]
  \foreach \x in {5.55,10.55} \draw[sheetGrey!40] (\x,3.1) -- (\x,-0.45);
  % ── 1 environments ──
  \node[pt] at (0,2.95) {\textcolor{sheetBlue}{1} environments, not frames};
  \node[sb, minimum width=24mm, anchor=west] (G) at (2.9,2.3) {Global env\\\texttt{g:1 outer:fn f:fn}};
  \node[sb, minimum width=24mm, anchor=west, draw=sheetGreen, fill=sheetGreen!10] (O) at (2.9,1.15) {\texttt{outer()} env\\\texttt{x:2} \emph{(kept alive)}};
  \node[sb, minimum width=18mm, anchor=west, draw=sheetOrange, fill=sheetOrange!8] (I) at (2.9,0.05) {fn \texttt{inner}\\\texttt{[[Environment]]}};
  \draw[flow] (O) -- node[lbl, right]{outer ref} (G);
  \draw[hot] (I) -- node[lbl, right]{captured} (O);
  \node[lbl, anchor=south, text=sheetGrey] at (G.north) {outer ref $\to$ null};
  % call stack
  \node[lbl, font=\tiny\bfseries] at (0.95,2.5) {call stack};
  \node[cell, minimum width=17mm, font=\tiny\ttfamily] (s0) at (0.95,2.15) {global ctx};
  \node[cell, minimum width=17mm, font=\tiny\ttfamily, dashed, draw=sheetRed, text=sheetRed] (s1) at (0.95,1.65) {outer() ctx};
  \node[lbl, text=sheetRed] at (0.95,1.25) {popped on return};
  \draw[flow, dashed] (s1.east) -- (O.west);
  \draw[flow] (s0.east) -- (G.west);
  \node[lbl, anchor=west, align=left] at (0,0.55) {\texttt{f = outer()}: ctx gone,\\env lives on: \texttt{f}$\to$\texttt{inner}$\to$env};
  \node[lbl, anchor=west, align=left, text=sheetBrown] at (0,-0.2) {lookup \texttt{g} in \texttt{inner}: own\\$\to$ outer() $\to$ global $\to$ ReferenceError};
  % ── 2 loop bindings ──
  \node[pt] at (5.65,2.95) {\textcolor{sheetBlue}{2} \texttt{for} + \texttt{setTimeout}};
  \node[lbl, anchor=west] at (5.65,2.5) {\texttt{for (var i=0; i<3; i++) setTimeout(()\fat log(i))}};
  \foreach \k in {0,1,2} \node[sb, minimum width=6mm, fill=white, draw=sheetOrange] (cv\k) at (6.3+0.8*\k,1.9) {c\k};
  \node[sb, minimum width=22mm, draw=sheetRed, fill=sheetRed!7] (vi) at (7.1,1.1) {ONE \texttt{i} (fn scope) = 3};
  \foreach \k in {0,1,2} \draw[flow] (cv\k) -- (vi);
  \node[lbl, text=sheetRed, font=\tiny\bfseries] at (9.5,1.1) {3 3 3};
  \node[lbl, anchor=west] at (5.65,0.55) {\texttt{for (let i=0; \ldots)} \ar{} a new env per iteration,};
  \node[lbl, anchor=west] at (5.65,0.33) {\texttt{i} \emph{copied} into it before the increment:};
  \foreach \k in {0,1,2} {
    \node[sb, minimum width=7mm, draw=sheetGreen, fill=sheetGreen!10] (li\k) at (6.2+1.1*\k,-0.2) {\texttt{i=\k}};
  }
  \node[lbl, text=sheetGreen!50!black, font=\tiny\bfseries] at (9.8,-0.2) {0 1 2};
  % ── 3 this decision ──
  \node[pt] at (10.65,2.95) {\textcolor{sheetBlue}{3} what is \texttt{this}? (first match)};
  \node[q] at (10.65,2.45) {arrow fn?};            \node[a] at (13.0,2.45) {outer \texttt{this} (lexical, fixed)};
  \node[q] at (10.65,2.0) {\texttt{new f()}?};      \node[a] at (13.0,2.0) {the fresh object};
  \node[q] at (10.65,1.55) {\texttt{f} from \texttt{bind(t)}?}; \node[a] at (13.0,1.55) {\texttt{t} (call/apply can't change)};
  \node[q] at (10.65,1.1) {\texttt{f.call(t)} / \texttt{apply}?}; \node[a] at (13.0,1.1) {\texttt{t}};
  \node[q] at (10.65,0.65) {\texttt{obj.f()} / \texttt{obj[k]()}?}; \node[a] at (13.0,0.65) {\texttt{obj} (the base ref)};
  \node[q, draw=sheetRed] at (10.65,0.2) {plain \texttt{f()}};   \node[a, text=sheetRed] at (13.0,0.2) {\texttt{undefined} (strict)};
  \node[a, text=sheetRed] at (13.0,-0.12) {\texttt{globalThis} (sloppy)};
  \foreach \y in {2.45,2.0,1.55,1.1,0.65,0.2} \draw[flow] (12.75,\y) -- (13.0,\y);
  \node[lbl, anchor=west, text=sheetBrown] at (10.65,-0.35) {sloppy call/apply: null$\to$globalThis, primitives boxed};
\end{tikzpicture}

\begin{multicols}{2}

\section{How it works — contexts and hoisting}
\begin{itemize}\raggedright
  \item Each call pushes an \textbf{execution context}: a \emph{LexicalEnvironment} (let/const/
        class, blocks), a \emph{VariableEnvironment} (var, function decls), the \texttt{this}
        binding, the realm. An \textbf{Environment Record} maps names $\to$ bindings and has an
        \texttt{[[OuterEnv]]} link: the \textbf{scope chain} (drawing 1).
  \item \textbf{Hoisting} = bindings are created when the scope is entered, before any line runs
        (\texttt{FunctionDeclarationInstantiation}). What differs is the initial state:
\end{itemize}
{\scriptsize\setlength{\tabcolsep}{2.5pt}
\begin{tabular}{@{}>{\raggedright}p{19mm}>{\raggedright}p{20mm}>{\raggedright}p{19mm}>{\raggedright\arraybackslash}p{13mm}@{}}
\toprule
declaration & at scope entry & use before the line & scope \\ \midrule
\texttt{var x} & \texttt{undefined} & \texttt{undefined} & function \\
\texttt{function f()\{\}} & the whole function & works & function\textsuperscript{*} \\
\texttt{let} / \texttt{const} & \emph{uninitialised} & ReferenceError (TDZ) & block \\
\texttt{class C} & \emph{uninitialised} & ReferenceError (TDZ) & block \\
\texttt{var f = ()\fat\{\}} & \texttt{f = undefined} & TypeError: not a fn & function \\ \bottomrule
\end{tabular}\\[1pt]
\textsuperscript{*}block-scoped in strict mode; sloppy block functions follow Annex B quirks.}
\begin{itemize}\raggedright
  \item \textbf{TDZ} runs from scope entry to the declaration \emph{executing} — it is
        temporal, not textual: a function \emph{called} after \texttt{let x=1} ran may read
        \texttt{x} even if written above it. \texttt{typeof x} in the TDZ throws too.
  \item Top-level \texttt{var}/\texttt{function} in a \emph{script} become \texttt{globalThis}
        properties; \texttt{let}/\texttt{const}/\texttt{class} do not. Modules have their own scope.
\end{itemize}

\section{Example — trace these}
\begin{lstlisting}[language=TSSheet]
var a = 1;
function f() { log(a); var a = 2; }  f(); // undefined
let b = 1;
{ log(b); let b = 2; }            // ReferenceError: TDZ
log(typeof h); var h = 1; function h() {}   // "function"
const o = { n: 'o', m() { return this.n; },
            later() { setTimeout(() => log(this.n)); } };
const m = o.m;  m();          // strict: TypeError (this undefined)
(o.m)();                      // 'o'  -- parens keep the reference
(0, o.m)();                   // loses this: comma yields a value
o.later();                    // 'o'  -- arrow reads later()'s this
o.m.bind({n:'x'}).call(o);    // 'x'  -- bind is permanent
function F() { log(this.n); }
new (F.bind({n:'x'}))();      // undefined -- new beats bind
\end{lstlisting}

\section{Closures — what is captured}
\begin{itemize}\raggedright
  \item The \textbf{binding}, by reference: later writes are visible to every closure sharing
        it. To freeze a value, pass it as an argument or copy it into a block \texttt{const}.
        (React's stale closure is this — see react-hooks-deep.)
  \item \textbf{Swift:} also captures variables by reference; a \emph{capture list}
        \texttt{[x]} copies at creation — JS has none. No \texttt{[weak self]} either: a tracing
        GC collects cycles; leaks are \emph{reachability} (listener/timer/cache holds the closure).
  \item V8: closures of one scope share one \textbf{Context} object with the captured vars — a
        big array used by \emph{any} inner fn lives while \emph{any} of them does.
\end{itemize}

\section{\texttt{this} — the rules (drawing 3)}
\begin{itemize}\raggedright
  \item \textbf{Arrows} have no own \texttt{this}, \texttt{arguments}, \texttt{super},
        \texttt{new.target}, \texttt{prototype}; \texttt{call/apply/bind} ignore their thisArg;
        \texttt{new} throws. In an object \emph{literal} an arrow sees the \emph{outer} \texttt{this}.
  \item \textbf{Lost \texttt{this}}: \texttt{arr.forEach(o.m)}, \texttt{const \{m\} = o},
        \texttt{setTimeout(o.m)} pass the \emph{function}, not the reference. Fix:
        \texttt{o.m.bind(o)}, a wrapper arrow, or an arrow \textbf{class field}
        \texttt{onTap = () \fat{} this.go()} — bound for life, but one fn \emph{per
        instance}, not on the prototype (no \texttt{super.onTap}, no prototype spy).
  \item Top level: \texttt{this} is \texttt{undefined} in an ES module, \texttt{module.exports}
        in CommonJS, \texttt{globalThis} in a sloppy script.
\end{itemize}

\section{Strict mode (\texttt{'use strict'}; classes + modules always)}
Plain-call \texttt{this} = \texttt{undefined} · assigning an undeclared name throws (sloppy
makes a global) · writing a read-only property throws (sloppy ignores) · no \texttt{with},
duplicate params, \texttt{010} · \texttt{arguments} stops aliasing params · \texttt{eval} gets
its own scope.

\section{Interview traps}
\begin{itemize}\raggedright
  \trap{``let/const aren't hoisted'' — they are, \emph{uninitialised} (TDZ).}
  \trap{``A closure copies the value'' — it keeps the variable (3 3 3); \texttt{let} fixes
        it with a fresh binding per iteration.}
  \trap{\texttt{this} is the \emph{call site}, not where the fn was defined — only arrows and
        \texttt{bind} fix it. \texttt{bind} twice: the first wins; \texttt{new} overrides it.}
\end{itemize}

\section{Remember}
\emph{Scope is where it's written · \texttt{this} is how it's called · closures keep variables,
not values · arrows borrow \texttt{this}.}

\section{Likely questions}
\begin{enumerate}\raggedright
  \item Closure? — a function + the environment it was created in, kept alive by the function.
  \item Why 3 3 3? — one function-scoped \texttt{var i}; callbacks run after the loop ends.
  \item \texttt{this} priority? — new $>$ bind $>$ call/apply $>$ method call $>$ default.
  \item Class method's \texttt{this} undefined in a callback? — detached, and class code is strict.
\end{enumerate}

\end{multicols}

\noindent{\footnotesize\color{sheetGrey}\textit{Related:} js-prototypes-objects-classes ·
js-async · js-types-coercion-modules · react-hooks-deep (stale closures) · Swift capture lists}

\end{document}
