Skip to content

Lesson 1.3.6 — Hoisting Inside a Function (var vs let trong Function) ​

Bài 1.3.6 — Hoisting in Function
Function Declarations, Expressions & Initialization Timing • 31 phút
0:00 / 0:00

0. Metadata (Thông tin bài học) ​

FieldValue
Stage1 — JavaScript Execution Model
Module1.3 — Hoisting & Temporal Dead Zone
Lesson1.3.6 — Hoisting Inside a Function
CompetencyC02.1 — Execution Context, C02.2 — Scope, C02.4 — Hoisting / TDZ
DepthL3–L4 (Use → Debug)
PrerequisitesLesson 1.3.1–1.3.5 (Declaration, var, let/const, TDZ, Mixed Declarations)
Cognitive LoadHigh

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:

js
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ể:

  1. Trace một function chứa mixed declarations (var, let, const, function) qua Creation Phase và Execution Phase.
  2. Dự đoán output/error của code có cả var và let trong cùng function mà không chạy.
  3. Giải thích bằng environment model tại sao var cho undefined còn let throw error khi truy cập trước dòng khai báo.
  4. Debug bug khi refactor var → let trong 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òn let ở 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 RecordEnvironment Record chứa tất cả bindings của một function body.
Mixed DeclarationsFunction chứa nhiều loại khai báo (var, let, const, function).
var trong FunctionD+I thành undefined trong Creation Phase của function.
let/const trong FunctionD trong Creation Phase, I tại dòng khai báo trong Execution Phase.
TDZ trong FunctionTừ đầ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) ​

  • var trong 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:

js
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.

ObservationClassificationReasoning
Thấy function configure(mode)Parameter bindingParameters đượ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 6var declarationvar trong function scope → declare + initialize thành undefined trong Function Environment Record.
Thấy let level ở dòng 7let declarationlet trong function scope → chỉ declare (tạo binding uninitialized). Chưa được initialize.
Thấy var debug bên trong ifvar re-declarationvar 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:

text
mode:    "prod"          (parameter — initialized)
debug:   undefined       (var — initialized)
level:   <uninitialized> (let — declared, TDZ)

Step 2 — Execution Phase

DòngCodeObservationReasoningOutput
2console.log(mode)Đọc parameterBinding mode đã initialized từ Creation Phase → truy cập hợp lệ."prod"
3console.log(debug)Đọc var bindingBinding debug đã initialized thành undefined → truy cập hợp lệ, giá trị là undefined.undefined
4console.log(level)Đọc let bindingBinding 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.

js
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: undefined vì var a đã D+I trong Creation Phase.
    • Dòng 3: ReferenceError: Cannot access 'b' before initialization vì let b chỉ D trong Creation Phase, chưa I.
  • Giải thích

    • Creation Phase: a → undefined; b → <uninitialized>.
    • Execution: console.log(a) → undefined; console.log(b) → TDZ error.

Prediction 2

Đừng chạy code. Dự đoán output hoặc error.

js
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 = 1 tạo binding trong Function Environment Record.
    • var x = 2 cũng tạo binding trong Function Environment Record (function scope, không phải block scope).
    • var x trong cùng scope với let 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 var và let trong 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.

js
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): 3 vì var i là function-scoped, tồn tại sau loop.
    • console.log(j): ReferenceError: j is not defined vì let j là block-scoped, chỉ tồn tại trong block for.
  • Giải thích

    • Creation Phase: var i → declare + initialize undefined.
    • Execution: loop chạy, i tăng đến 3. let j được tạo mỗi iteration nhưng bị hủy sau mỗi iteration.
    • Sau loop: i vẫn tồn tại (function scope) → 3. j không tồn tại (block scope) → ReferenceError.

Transfer Check — Không chạy code

Bạn đang review đoạn code sau:

js
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:

  1. Dòng console.log(options.debug) in gì? Tại sao options.debug an toàn dù chưa được gán, nhưng let mode lại gây nguy hiểm nếu đọc trước?
  2. Nếu đổi var debug = true thành let debug = true, điều gì thay đổi ở Creation Phase?
[Đáp án tham khảo]
  • Câu 1: options.debug in undefined vì đâ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 mode trong 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 = true trong block if tạo một block-scoped binding riêng. Ở Creation Phase, engine sẽ declare debug trong block scope của if, không phải trong Function Environment Record. Dòng console.log(debug) sau if sẽ throw ReferenceError: debug is not defined vì block-scoped debug đã bị hủy khi ra khỏi if.

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:

js
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:

js
function setup() {
  console.log(a);
  console.log(b);
  var a = 10;
  let b = 20;
}
Phaseab
Creation Phase start______
Execution, dòng 2______
Execution, dòng 3______
Execution, dòng 5______
Execution, dòng 6______
[Đáp án]
Phaseab
Creation Phase startundefined<uninitialized>
Execution, dòng 2undefined<uninitialized> (TDZ)
Execution, dòng 3undefined<uninitialized> (TDZ) → throw
Execution, dòng 510<uninitialized> (TDZ)
Execution, dòng 61020

Level 3 — Independent (Tự thực hiện) ​

Viết một function ngắn (tối đa 10 dòng) trong đó:

  1. Có ít nhất một var và một let.
  2. Có ít nhất một console.log trước dòng khai báo var (in undefined).
  3. Có ít nhất một console.log trước dòng khai báo let (throw ReferenceError).
  4. Có một block (if hoặc for) chứa var và let cùng tên với outer scope.
  5. 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 ​

js
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) ​

js
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:

  1. Trong Creation Phase của inner, engine phát hiện var x → tạo binding x = undefined trong Function Environment Record của inner.
  2. Khi console.log(x) chạy, engine resolve x theo Lexical Environment:
    • Tìm trong inner's Environment Record trước → thấy x (đã initialized thành undefined).
    • Không bao giờ đi ra outer để tìm vì binding đã được tìm thấy ở scope gần nhất.
  3. Đâ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 ​

js
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.

js
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, sau validate.

Hypothesis (Giả thuyết):

  • let result trong cùng function scope đưa binding vào TDZ từ đầu function.
  • validate(result) chạy trước dòng let result = ... → binding trong TDZ → ReferenceError.
  • Với var, binding được initialize thành undefined trong 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:

js
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:

js
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:

js
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:

  1. Code có bug không? Nếu có, bug gì?
  2. Nếu refactor var → let, bạn cần làm gì ngoài đổi từ khóa?
  3. Viết lại function bằng let/const.
[Đáp án tham khảo]
  • Bạn nghĩ

    • Dòng 2 console.log("Rendering:", name) đọc var name trướ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ẽ throw ReferenceError (tốt hơn — fail fast).
  • Refactor với let/const:

    js
    function 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 const và 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/else block chỉ để gán biến.

12. Production Scenario (Tình huống thực tế) ​

Bạn debug một production crash sau khi merge PR refactor var → const:

js
// 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:

  1. Tại sao code trước chạy được?
  2. Tại sao code sau throw SyntaxError?
  3. Fix đúng là gì?
[Đáp án tham khảo]
  • Câu 1: var settings re-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: const không cho phép re-declare trong cùng scope. const settings dòng 4 là SyntaxError tại parse time.

  • Câu 3:

    js
    function initWidget(config) {
      let settings = config || {};
      applyDefaults(settings);
      settings = merge(settings, globalSettings);
      return settings;
    }

    Hoặc refactor để không cần gán lại:

    js
    function 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)

  1. 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:

    js
    function test() {
      console.log(a);
      var a = 1;
      let b = 2;
    }
  2. Hỏi AI: "Trace this function step by step. What happens in Creation Phase and Execution Phase?"

    js
    function test() {
      console.log(a);
      var a = 1;
      let b = 2;
    }
  3. So sánh:

    • Copy chính xác đoạn AI nói về let và 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 b qua Creation Phase không? Hay nó bỏ qua bước declare?
  4. 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..."

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.
  • AI trả lời (mô phỏng phản hồi thực tế)

    • "In the Creation Phase, var a is hoisted and initialized with undefined."
    • "let b is not hoisted, so it doesn't exist until the declaration line."
    • "When console.log(a) runs, a is undefined because of hoisting."
    • "b would throw a ReferenceError if accessed before its declaration."
  • So sánh

    • AI đúng về behavior: a là undefined, b throw error nếu truy cập sớm.
    • AI sai về mechanism: Nó nói "let b is not hoisted" — binding b đã tồn tại trong Creation Phase, chỉ là chưa initialize.
    • AI không trace let b qua Creation Phase — nó bỏ qua bước declare.
  • Điểm AI nói sai hoặc quá mơ hồ

    • AI nói "let is not hoisted" — câu này sai về mechanism. let binding được tạo trong Creation Phase.
    • AI nói "var is 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.
  • Kết luận

    • Nếu bạn chỉ ra được rằng cả var và 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).

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ả var và let. Hãy trace qua Creation Phase và Execution Phase. Giải thích tại sao var cho undefined còn let throw 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'."

js
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ành undefined. Cho nên khi Execution Phase chạy đến console.log(x), binding x đã có giá trị hợp lệ — dù là undefined."
    • "let y chỉ được declare trong Creation Phase. Binding y tồn tại trong Function Environment Record nhưng ở trạng thái uninitialized."
    • "Khi console.log(y) chạy, engine thấy binding y tồn tại nhưng chưa initialize → throw ReferenceError: Cannot access 'y' before initialization."
    • "Dòng let y = 20 sau đó mới initialize binding y. Từ đó trở đi, y mới dùng được."
    • "Vậy nên không phải var được 'đưa lên đầu' còn let không. Cả hai đều được xử lý trong Creation Phase. Khác biệt là initialize timing — var initialize ngay, let deferred."

💡 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. var là khách được giao vé tạm (undefined) ngay khi vào danh sách. let là 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 undefined khác với uninitialized khô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 var và let có 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 functionImplementation (Thực hành trace)L3
Dự đoán output/error của function có var + letPrediction (Dự đoán)L3–L4
Giải thích bằng environment modelExplain (Giải thích) + Teach BackL3
Debug refactor var → let trong functionDebug (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ả var và let trong cùng function mà không chạy.
  • [ ] Có thể giải thích bằng environment model tại sao var cho undefined còn let throw error khi truy cập trước dòng khai báo.
  • [ ] Có thể debug bug khi refactor var → let trong 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 var và let trong 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 let binding trong TDZ có thể gây ReferenceError ngay cả bên trong closure.
  • Stage 2 (Object Model): this và prototype lookup cũng phụ thuộc vào execution context và environment.
  • Stage 8 (React Hooks): useState closures và stale closure bugs đều bắt nguồn từ environment retention mà bạn sẽ học ở Module 1.4.
📴 Offline Mode — Content served from cache