Lesson 1.3.6 — Hoisting Inside a Function (var vs let trong Function)
0. Metadata (Thông tin bài học)
| Field | Value |
|---|---|
| Stage | 1 — JavaScript Execution Model |
| Module | 1.3 — Hoisting & Temporal Dead Zone |
| Lesson | 1.3.6 — Hoisting Inside a Function |
| Competency | C02.1 — Execution Context, C02.2 — Scope, C02.4 — Hoisting / TDZ |
| Depth | L3–L4 (Use → Debug) |
| Prerequisites | Lesson 1.3.1–1.3.5 (Declaration, var, let/const, TDZ, Mixed Declarations) |
| Cognitive Load | High |
1. Why This Exists (Vì sao cần học)
Bạn đã học var, let, const, function, class như những khái niệm riêng lẻ. Giờ bạn cần đặt chúng vào cùng một function và trace bằng tay — không dùng "var hoisted, let không" như một câu thần chú.
Hãy xem hai đoạn code này:
function testVar() {
console.log(a);
var a = 10;
}
function testLet() {
console.log(a);
let a = 10;
}Một cái in undefined. Một cái throw ReferenceError. Nếu bạn chỉ nhớ "var hoisted, let không", bạn sẽ không giải thích được tại sao cả hai đều "tồn tại" trong scope trước dòng khai báo, nhưng một cái cho phép đọc và một cái không.
Bài này là bài tổng hợp Module 1.3. Bạn sẽ trace một function chứa cả var và let qua Creation Phase và Execution Phase, bằng đúng environment model.
2. Prerequisites (Yêu cầu đầu vào)
Trước khi học bài này, bạn cần:
- Phân biệt Declaration, Initialization, Assignment (Lesson 1.3.1).
- Hiểu
varđược D+I trong Creation Phase (Lesson 1.3.2). - Hiểu
letđược D trong Creation Phase, I tại dòng khai báo (Lesson 1.3.3). - Hiểu TDZ là trạng thái uninitialized (Lesson 1.3.4).
- Hiểu "hoisting" không phải một cơ chế duy nhất (Lesson 1.3.5).
3. Learning Objectives (Mục tiêu học tập)
Sau bài này, bạn có thể:
- Trace một function chứa mixed declarations (
var,let,const,function) qua Creation Phase và Execution Phase. - Dự đoán output/error của code có cả
varvàlettrong cùng function mà không chạy. - Giải thích bằng environment model tại sao
varchoundefinedcònletthrow error khi truy cập trước dòng khai báo. - Debug bug khi refactor
var→lettrong function body.
4. Mental Model (Mô hình tư duy)
Mental Model: Function Body là một Scope với Environment Record Riêng
function example() {
console.log(x); // Dòng 2
var x = 1; // Dòng 3
let y = 2; // Dòng 4
}
example();Creation Phase (khi example() được gọi):
1. Quét toàn bộ function body
2. Tìm var x → declare + initialize x = undefined trong Function Environment Record
3. Tìm let y → declare y (uninitialized) trong Function Environment Record
4. Tìm function declarations → declare + initialize (nếu có)Execution Phase:
Dòng 2: console.log(x) → x đã initialize (undefined) → in undefined
Dòng 3: x = 1 → assignment
Dòng 4: let y = 2 → initialize y = 2Điểm then chốt: Cả var và let đều được declare trong Creation Phase. Khác biệt duy nhất là initialize timing.
Sai lầm phổ biến
"
varđược đưa lên đầu function, cònletở lại chỗ cũ."
Sai. Cả hai đều được xử lý trong Creation Phase. var được initialize ngay trong Creation Phase. let chỉ được declare trong Creation Phase, initialize tại dòng khai báo. Không có code nào "di chuyển".
5. Core Concepts (Các khái niệm cốt lõi)
Essential (Bắt buộc)
| Concept | Định nghĩa |
|---|---|
| Function Environment Record | Environment Record chứa tất cả bindings của một function body. |
| Mixed Declarations | Function chứa nhiều loại khai báo (var, let, const, function). |
var trong Function | D+I thành undefined trong Creation Phase của function. |
let/const trong Function | D trong Creation Phase, I tại dòng khai báo trong Execution Phase. |
| TDZ trong Function | Từ đầu function body đến dòng khai báo let/const. |
Supporting (Hỗ trợ)
- Parameter Scope: Parameters tạo bindings riêng trong Function Environment Record, trước cả body declarations.
Awareness (Biết tồn tại)
vartrong function không pollute global object (khác với global scope).- Strict mode có thể thay đổi behavior của function declaration trong block.
Out of Scope (Không học trong bài này)
- Chi tiết spec của
NewDeclarativeEnvironment,FunctionInitialize. - Closure mechanism (sẽ học ở Module 1.4).
6. Worked Example (Ví dụ phân tích từng bước)
Trace đoạn code sau:
function configure(mode) {
console.log(mode);
console.log(debug);
console.log(level);
var debug = true;
let level = "verbose";
if (mode === "prod") {
var debug = false;
let level = "error";
console.log(level);
}
console.log(debug);
console.log(level);
}
configure("prod");Step 1 — Creation Phase (Function Execution Context của configure)
Engine quét toàn bộ function body để thiết lập Function Environment Record.
| Observation | Classification | Reasoning |
|---|---|---|
Thấy function configure(mode) | Parameter binding | Parameters được xử lý trước body. mode được declare + initialize với argument "prod" ngay lập tức. |
Thấy var debug ở dòng 6 | var declaration | var trong function scope → declare + initialize thành undefined trong Function Environment Record. |
Thấy let level ở dòng 7 | let declaration | let trong function scope → chỉ declare (tạo binding uninitialized). Chưa được initialize. |
Thấy var debug bên trong if | var re-declaration | var không có block scope. Binding debug đã tồn tại trong Function Environment Record → no-op ở Creation Phase. |
Conclusion — Trạng thái Environment Record sau Creation Phase:
mode: "prod" (parameter — initialized)
debug: undefined (var — initialized)
level: <uninitialized> (let — declared, TDZ)Step 2 — Execution Phase
| Dòng | Code | Observation | Reasoning | Output |
|---|---|---|---|---|
| 2 | console.log(mode) | Đọc parameter | Binding mode đã initialized từ Creation Phase → truy cập hợp lệ. | "prod" |
| 3 | console.log(debug) | Đọc var binding | Binding debug đã initialized thành undefined → truy cập hợp lệ, giá trị là undefined. | undefined |
| 4 | console.log(level) | Đọc let binding | Binding level tồn tại nhưng ở trạng thái uninitialized (TDZ). Engine không cho phép đọc trước khi initialize. | ReferenceError |
Production Risk
Nếu bạn refactor var debug → let debug trong function body mà không kiểm tra tất cả dòng code trước dòng khai báo, bạn sẽ biến undefined thành ReferenceError — crash production.
7. Prediction Exercise (Bài tập dự đoán)
Prediction 1
Đừng chạy code. Dự đoán output hoặc error cho từng dòng, giải thích bằng Creation Phase / Execution Phase.
function demo() {
console.log(a);
console.log(b);
var a = 1;
let b = 2;
}
demo();[Đáp án & Giải thích]
Bạn nghĩ
- Dòng 2:
undefinedvìvar ađã D+I trong Creation Phase. - Dòng 3:
ReferenceError: Cannot access 'b' before initializationvìlet bchỉ D trong Creation Phase, chưa I.
- Dòng 2:
Giải thích
- Creation Phase:
a→undefined;b→<uninitialized>. - Execution:
console.log(a)→undefined;console.log(b)→ TDZ error.
- Creation Phase:
Prediction 2
Đừng chạy code. Dự đoán output hoặc error.
function test() {
let x = 1;
if (true) {
var x = 2;
}
console.log(x);
}
test();[Đáp án & Giải thích]
Bạn nghĩ
SyntaxError: Identifier 'x' has already been declared.
Giải thích
let x = 1tạo binding trong Function Environment Record.var x = 2cũng tạo binding trong Function Environment Record (function scope, không phải block scope).var xtrong cùng scope vớilet x→ re-declaration không được phép →SyntaxError(early error).- Quan trọng: Lỗi này xảy ra trước cả Creation Phase — engine phát hiện xung đột khi parse function body, trước khi bất kỳ execution context nào được tạo. Đây không phải runtime error.
- Điều này chứng minh
varvàlettrong cùng function scope xung đột, không phải "var ở dưới, let ở trên".
Prediction 3
Đừng chạy code. Dự đoán output hoặc error.
function loop() {
for (var i = 0; i < 3; i++) {
let j = i;
}
console.log(i);
console.log(j);
}
loop();[Đáp án & Giải thích]
Bạn nghĩ
console.log(i):3vìvar ilà function-scoped, tồn tại sau loop.console.log(j):ReferenceError: j is not definedvìlet jlà block-scoped, chỉ tồn tại trong blockfor.
Giải thích
- Creation Phase:
var i→ declare + initializeundefined. - Execution: loop chạy,
ităng đến3.let jđược tạo mỗi iteration nhưng bị hủy sau mỗi iteration. - Sau loop:
ivẫn tồn tại (function scope) →3.jkhông tồn tại (block scope) →ReferenceError.
- Creation Phase:
Transfer Check — Không chạy code
Bạn đang review đoạn code sau:
function handleConfig(options) {
console.log(options.mode);
console.log(options.debug);
if (options.verbose) {
var debug = true;
}
let mode = options.mode || "default";
console.log(debug);
console.log(mode);
}
handleConfig({ mode: "prod", verbose: false });Câu hỏi:
- Dòng
console.log(options.debug)in gì? Tại saooptions.debugan toàn dù chưa được gán, nhưnglet modelại gây nguy hiểm nếu đọc trước? - Nếu đổi
var debug = truethànhlet debug = true, điều gì thay đổi ở Creation Phase?
[Đáp án tham khảo]
Câu 1:
options.debuginundefinedvì đây là property access trên object argument — JavaScript không throw khi đọc property không tồn tại. Điều này khác hoàn toàn với identifier resolution (let modetrong TDZ throw ReferenceError). Đây là sự khác biệt giữa object property lookup và environment record binding lookup.Câu 2:
let debug = truetrong blockiftạo một block-scoped binding riêng. Ở Creation Phase, engine sẽ declaredebugtrong block scope củaif, không phải trong Function Environment Record. Dòngconsole.log(debug)sauifsẽ throwReferenceError: debug is not definedvì block-scopeddebugđã bị hủy khi ra khỏiif.
8. Implementation Lab (Bài lab thực hành)
Level 1 — Guided (Hướng dẫn)
Viết comment mô tả phase cho đoạn code sau:
function init() {
// Creation Phase: var config → D + I = undefined
// Creation Phase: let mode → D (uninitialized)
console.log(config); // undefined
console.log(mode); // ReferenceError!
var config = { env: "prod" };
let mode = "active";
}Level 2 — Partial Scaffold (Khung mẫu một phần)
Điền trạng thái Environment Record sau từng phase:
function setup() {
console.log(a);
console.log(b);
var a = 10;
let b = 20;
}| Phase | a | b |
|---|---|---|
| Creation Phase start | ___ | ___ |
| Execution, dòng 2 | ___ | ___ |
| Execution, dòng 3 | ___ | ___ |
| Execution, dòng 5 | ___ | ___ |
| Execution, dòng 6 | ___ | ___ |
[Đáp án]
| Phase | a | b |
|---|---|---|
| Creation Phase start | undefined | <uninitialized> |
| Execution, dòng 2 | undefined | <uninitialized> (TDZ) |
| Execution, dòng 3 | undefined | <uninitialized> (TDZ) → throw |
| Execution, dòng 5 | 10 | <uninitialized> (TDZ) |
| Execution, dòng 6 | 10 | 20 |
Level 3 — Independent (Tự thực hiện)
Viết một function ngắn (tối đa 10 dòng) trong đó:
- Có ít nhất một
varvà mộtlet. - Có ít nhất một
console.logtrước dòng khai báovar(inundefined). - Có ít nhất một
console.logtrước dòng khai báolet(throwReferenceError). - Có một block (
ifhoặcfor) chứavarvàletcùng tên với outer scope. - Giải thích bằng đúng 3 khái niệm: Function Environment Record, Creation Phase, TDZ.
9. Edge Cases (Các trường hợp ngoại lệ)
Edge Case 1: var và let cùng tên trong cùng function
function clash() {
let x = 1;
var x = 2; // SyntaxError: Identifier 'x' has already been declared
}var và let trong cùng function scope là re-declaration, bị cấm. Điều này chứng minh cả hai đều tạo binding trong cùng một Function Environment Record — không phải "var ở một scope, let ở scope khác".
TIP
Trong production, ESLint rule no-redeclare bắt lỗi này. Nhưng nếu bạn disable linter hoặc dùng dynamic code generation, bug này có thể xuất hiện tại runtime.
Edge Case 2: var trong nested function (Shadowing)
function outer() {
var x = 1;
function inner() {
console.log(x); // undefined
var x = 2;
console.log(x); // 2
}
inner();
}Observation: Dòng console.log(x) đầu tiên in undefined, không phải 1.
Mechanism — Shadowing bằng hoisting:
- Trong Creation Phase của
inner, engine phát hiệnvar x→ tạo bindingx = undefinedtrong Function Environment Record củainner. - Khi
console.log(x)chạy, engine resolvextheo Lexical Environment:- Tìm trong
inner's Environment Record trước → thấyx(đã initialized thànhundefined). - Không bao giờ đi ra
outerđể tìm vì binding đã được tìm thấy ở scope gần nhất.
- Tìm trong
- Đây gọi là shadowing: binding trong inner scope "che mờ" binding cùng tên ở outer scope.
WARNING
Điều này khác với "ghi đè" (overwrite). outer's x vẫn tồn tại với giá trị 1, nhưng bị che bởi inner's x trong suốt thời gian inner thực thi.
Edge Case 3: Parameter và var cùng tên
function foo(a) {
var a = 10;
console.log(a); // 10
}
foo(5);var a trong function body với cùng tên parameter là no-op ở Creation Phase (binding đã tồn tại). Dòng var a = 10 chỉ là assignment. Điều này khác với let a — let a với cùng tên parameter sẽ throw SyntaxError.
10. Debug Lab (Bài lab gỡ lỗi)
Symptom (Triệu chứng): Developer refactor var → let trong một function lớn. Test pass trong local nhưng fail trên CI với ReferenceError.
function processData(data) {
if (data.length > 0) {
var result = data[0];
}
validate(result);
// ... 50 dòng code ...
let result = transform(result); // ← refactor từ var sang let
return result;
}Reproduction (Tái hiện lỗi): Khi đổi var result thành let result, validate(result) throw ReferenceError.
Evidence (Bằng chứng):
- Stack trace chỉ dòng
validate(result). - Code hoạt động với
var. let resultở cuối function, sauvalidate.
Hypothesis (Giả thuyết):
let resulttrong cùng function scope đưa binding vào TDZ từ đầu function.validate(result)chạy trước dònglet result = ...→ binding trong TDZ →ReferenceError.- Với
var, binding được initialize thànhundefinedtrong Creation Phase →validate(undefined)chạy (có thể sai logic nhưng không crash).
Verification (Xác minh): Di chuyển let result lên trước validate:
function processData(data) {
let result;
if (data.length > 0) {
result = data[0];
}
validate(result);
// ...
result = transform(result);
return result;
}Code chạy. Giả thuyết đúng.
Root Cause (Nguyên nhân gốc rễ): Developer refactor từng dòng var → let mà không hiểu let binding trong cùng scope tạo TDZ từ đầu function. Họ nghĩ "đổi từ khóa là đủ".
Fix (Sửa): Di chuyển let result lên đầu function và tách declaration khỏi assignment:
function processData(data) {
let result;
if (data.length > 0) {
result = data[0];
}
validate(result);
result = transform(result);
return result;
}Prevention (Phòng ngừa):
- Khi refactor
var→let, luôn kiểm tra tất cả usage trước dòng khai báo. - Dùng
letở đầu block/function khi có thể, tách declaration và assignment rõ ràng.
11. Design Exercise (Bài tập thiết kế giải pháp)
Depth L3–L4: Decision (Quyết định)
Bạn đang review code sau:
function renderUser(user) {
console.log("Rendering:", name);
if (user.isAdmin) {
var name = user.adminName;
var badge = "admin";
} else {
var name = user.guestName;
var badge = "guest";
}
console.log(name, badge);
}Câu hỏi:
- Code có bug không? Nếu có, bug gì?
- Nếu refactor
var→let, bạn cần làm gì ngoài đổi từ khóa? - Viết lại function bằng
let/const.
[Đáp án tham khảo]
Bạn nghĩ
- Dòng 2
console.log("Rendering:", name)đọcvar nametrước assignment →undefined. - Đây là bug silent: không crash nhưng in
undefined. - Nếu đổi
var name→let name, dòng 2 sẽ throwReferenceError(tốt hơn — fail fast).
- Dòng 2
Refactor với let/const:
jsfunction renderUser(user) { const name = user.isAdmin ? user.adminName : user.guestName; const badge = user.isAdmin ? "admin" : "guest"; console.log("Rendering:", name); console.log(name, badge); }Trade-off
- Dùng
constvà ternary expression loại bỏ hoàn toàn TDZ risk. - Code ngắn hơn, rõ ràng hơn, không cần
if/elseblock chỉ để gán biến.
- Dùng
12. Production Scenario (Tình huống thực tế)
Bạn debug một production crash sau khi merge PR refactor var → const:
// Trước
function initWidget(config) {
var settings = config || {};
applyDefaults(settings);
var settings = merge(settings, globalSettings);
return settings;
}
// Sau
function initWidget(config) {
const settings = config || {};
applyDefaults(settings);
const settings = merge(settings, globalSettings); // SyntaxError!
return settings;
}Câu hỏi:
- Tại sao code trước chạy được?
- Tại sao code sau throw
SyntaxError? - Fix đúng là gì?
[Đáp án tham khảo]
Câu 1:
var settingsre-declare được trong cùng scope. Dòng 2 tạo binding, dòng 4 chỉ là assignment (vì binding đã tồn tại).applyDefaults(settings)đọc binding đã initialize (config || {}).Câu 2:
constkhông cho phép re-declare trong cùng scope.const settingsdòng 4 là SyntaxError tại parse time.Câu 3:
jsfunction initWidget(config) { let settings = config || {}; applyDefaults(settings); settings = merge(settings, globalSettings); return settings; }Hoặc refactor để không cần gán lại:
jsfunction initWidget(config) { const baseSettings = config || {}; const settings = merge(baseSettings, globalSettings); applyDefaults(settings); return settings; }
13. AI-assisted Exercise (Bài tập với AI)
Level C — Delegate & Inspect (Giao phó & Kiểm tra)
Tự trả lời trước: Viết ra giấy trace của bạn cho đoạn code sau, qua Creation Phase và Execution Phase:
jsfunction test() { console.log(a); var a = 1; let b = 2; }Hỏi AI: "Trace this function step by step. What happens in Creation Phase and Execution Phase?"
jsfunction test() { console.log(a); var a = 1; let b = 2; }So sánh:
- Copy chính xác đoạn AI nói về
letvàvar. - Xác định từ khóa nào trong câu trả lời AI là dấu hiệu của approximation (ví dụ: "let is not hoisted", "var moves to the top", "let stays in place").
- AI có trace
let bqua Creation Phase không? Hay nó bỏ qua bước declare?
- Copy chính xác đoạn AI nói về
Verify & Decide:
- Kiểm tra bằng MDN hoặc ECMAScript spec (từ khóa:
Function Environment Record,CreateMutableBinding,InitializeBinding). - Viết một câu phản biện lại AI nếu nó nói sai, dùng đúng terminology: "Binding được tạo trong Creation Phase, khác biệt là initialize timing..."
- Kiểm tra bằng MDN hoặc ECMAScript spec (từ khóa:
Gợi ý
Để ý xem AI có nói "let is not hoisted" không. Nếu có, đó là dấu hiệu mental model của AI đang dùng approximation. Bạn đã học rằng let được declare trong Creation Phase — nó chỉ không được initialize.
[Đáp án tham khảo]
Bạn nghĩ
- Creation Phase:
var a→ D + I =undefined;let b→ D (uninitialized). - Execution:
console.log(a)→undefined;a = 1→ assignment;let b = 2→ I + A.
- Creation Phase:
AI trả lời (mô phỏng phản hồi thực tế)
- "In the Creation Phase,
var ais hoisted and initialized withundefined." - "
let bis not hoisted, so it doesn't exist until the declaration line." - "When
console.log(a)runs,aisundefinedbecause of hoisting." - "
bwould throw a ReferenceError if accessed before its declaration."
- "In the Creation Phase,
So sánh
- AI đúng về behavior:
alàundefined,bthrow error nếu truy cập sớm. - AI sai về mechanism: Nó nói "
let bis not hoisted" — bindingbđã tồn tại trong Creation Phase, chỉ là chưa initialize. - AI không trace
let bqua Creation Phase — nó bỏ qua bước declare.
- AI đúng về behavior:
Điểm AI nói sai hoặc quá mơ hồ
- AI nói "
letis not hoisted" — câu này sai về mechanism.letbinding được tạo trong Creation Phase. - AI nói "
varis hoisted" — lại dùng "hoisted" như cơ chế thay vì hệ quả của Creation Phase. - AI không nhắc đến Environment Record hoặc initialization timing.
- AI nói "
Kết luận
- Nếu bạn chỉ ra được rằng cả
varvàletđều được declare trong Creation Phase, và sự khác biệt duy nhất là initialize timing, bạn đã hiểu sâu hơn AI. Concept này sẽ quay lại ở Module 1.4 (Closure) và Stage 8 (React Hooks).
- Nếu bạn chỉ ra được rằng cả
14. Teach Back (Dạy lại)
Yêu cầu: Giải thích cho một đồng nghiệp junior trong 2 phút:
"Tôi có một function chứa cả
varvàlet. Hãy trace qua Creation Phase và Execution Phase. Giải thích tại saovarchoundefinedcònletthrow error khi đọc trước dòng khai báo. Dùng đúng terminology: Function Environment Record, declare, initialize. Không được dùng từ 'hoisted' như một cơ chế — nếu cần nhắc đến, hãy gọi đó là 'hệ quả của Creation Phase'."
function example() {
console.log(x);
console.log(y);
var x = 10;
let y = 20;
}
example();Mô phỏng
- Bạn nói
- "Khi
example()được gọi, engine tạo Function Execution Context và chạy Creation Phase." - "Trong Creation Phase, engine quét toàn bộ function body và tạo bindings trong Function Environment Record."
- "
var xđược declare VÀ initialize thànhundefined. Cho nên khi Execution Phase chạy đếnconsole.log(x), bindingxđã có giá trị hợp lệ — dù làundefined." - "
let ychỉ được declare trong Creation Phase. Bindingytồn tại trong Function Environment Record nhưng ở trạng thái uninitialized." - "Khi
console.log(y)chạy, engine thấy bindingytồn tại nhưng chưa initialize → throwReferenceError: Cannot access 'y' before initialization." - "Dòng
let y = 20sau đó mới initialize bindingy. Từ đó trở đi,ymới dùng được." - "Vậy nên không phải
varđược 'đưa lên đầu' cònletkhông. Cả hai đều được xử lý trong Creation Phase. Khác biệt là initialize timing —varinitialize ngay,letdeferred."
- "Khi
💡 Liên tưởng: Tưởng tượng một hội trường. Creation Phase là lúc nhân viên lập danh sách khách.
varlà khách được giao vé tạm (undefined) ngay khi vào danh sách.letlà khách có tên trong danh sách nhưng chưa có vé — nếu cố vào hội trường trước khi nhận vé, bị bảo vệ đuổi ra (ReferenceError). Không ai "di chuyển" lên đầu danh sách cả.
Gợi ý đánh giá bản thân
- Đồng nghiệp có hiểu tại sao
undefinedkhác vớiuninitializedkhông? - Bạn có thể dự đoán behavior nếu thêm
function foo() {}vào function không? - Nếu đồng nghiệp hỏi "vậy
varvàletcó cùng scope không?", bạn trả lời được không?
15. Assessment (Đánh giá)
| Objective (Mục tiêu) | Hình thức đánh giá | Depth |
|---|---|---|
| Trace mixed declarations trong function | Implementation (Thực hành trace) | L3 |
Dự đoán output/error của function có var + let | Prediction (Dự đoán) | L3–L4 |
| Giải thích bằng environment model | Explain (Giải thích) + Teach Back | L3 |
Debug refactor var → let trong function | Debug (Gỡ lỗi) | L4 |
16. Exit Criteria (Tiêu chí qua bài)
- [ ] Có thể trace một function chứa mixed declarations (
var,let,const,function) qua Creation Phase và Execution Phase. - [ ] Có thể dự đoán đúng output/error của code có cả
varvàlettrong cùng function mà không chạy. - [ ] Có thể giải thích bằng environment model tại sao
varchoundefinedcònletthrow error khi truy cập trước dòng khai báo. - [ ] Có thể debug bug khi refactor
var→lettrong function body. - [ ] Có thể dự đoán đúng 3/3 scenarios trong Prediction Exercise mà không chạy code.
17. Spiral Connection (Liên kết xoắn ốc)
Previous (Trước): Hoisting không phải một cơ chế duy nhất → bạn đã biết 5 loại khai báo có declaration/initialization timing khác nhau.
Current (Hiện tại): Hoisting trong Function → bạn hiểu rằng Function Environment Record chứa tất cả bindings, và sự khác biệt giữa
varvàlettrong cùng function là initialize timing.
Next (Tiếp theo):
- Module 1.4 (Closure): Khi một inner function được return ra ngoài, nó giữ một reference đến Environment Record của outer function. Nếu bạn không biết chính xác Function Environment Record chứa những binding nào (
var,let,const,function) và chúng ở trạng thái gì (initialized vs uninitialized), bạn sẽ không thể giải thích:
- Tại sao closure "nhớ" được giá trị.
- Tại sao stale closure lại giữ giá trị cũ thay vì giá trị mới nhất.
- Tại sao một
letbinding trong TDZ có thể gây ReferenceError ngay cả bên trong closure.- Stage 2 (Object Model):
thisvà prototype lookup cũng phụ thuộc vào execution context và environment.- Stage 8 (React Hooks):
useStateclosures và stale closure bugs đều bắt nguồn từ environment retention mà bạn sẽ học ở Module 1.4.