Skip to content

Lesson 1.4.2 — Closure và Lifetime ​

Bài 1.4.2 — Closure và Lifetime
Reachability, Environment Lifetime & Retained State • 28 phút
0:00 / 0:00

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

FieldValue
Stage1
Module1.4 — Closures
Lesson1.4.2 — Closure và Lifetime
CompetencyC02 — JavaScript Runtime (C02.5 Closure)
Depth TargetL4 (Debug)
PrerequisitesClosure Formation (1.4.1), Lexical Environment (1.2.7), Variable Resolution (1.2.6), Execution Context Stack (1.1.5)
Estimated Cognitive LoadHigh

Out of Scope (Ngoài phạm vi)

  • Garbage Collection algorithms và memory profiling (Stage 11)
  • Weak references, WeakMap, WeakRef, FinalizationRegistry (Stage 11)
  • Stale Closure (Lesson 1.4.8)
  • Async scheduling / Event Loop (Stage 3)
  • React lifecycle và Hooks (Stage 8)

1. Why This Exists (Vì sao cần học) ​

Ở Lesson 1.4.1, bạn đã thấy inner function vẫn truy cập được outer variable sau khi outer function return:

js
function outer() {
  const message = "hello";

  return function inner() {
    return message;
  };
}

const fn = outer();
console.log(fn()); // "hello"

Nhưng còn một câu hỏi sâu hơn:

outer() đã kết thúc. Execution Context của outer đã bị pop khỏi Call Stack. Vậy dữ liệu mà inner cần đang tồn tại ở đâu, và tồn tại đến bao lâu?

Nếu chỉ ghi nhớ câu "closure nhớ biến", bạn sẽ dễ hình thành mental model sai rằng:

  • Function return thì mọi local variable phải bị xóa ngay.
  • Closure copy giá trị sang một vùng memory riêng.
  • Outer environment tồn tại vĩnh viễn sau khi có closure.
  • Execution Context và Lexical Environment có cùng lifetime.
  • Một closure tồn tại đồng nghĩa chắc chắn có memory leak.

Lesson này tách riêng execution lifetime khỏi environment reachability. Đây là bước đầu để sau này bạn reasoning được callback lifecycle, stale closure và memory retention bằng cùng một mental model.

2. Prerequisites (Yêu cầu đầu vào) ​

Trước khi học bài này, bạn phải:

  • [ ] Giải thích được Closure Formation ở Lesson 1.4.1.
  • [ ] Biết inner function giữ reference cần thiết đến lexical environment nơi nó được tạo.
  • [ ] Phân biệt Execution Context với Lexical Environment.
  • [ ] Biết Function Execution Context được push khi function được gọi và pop khi function kết thúc.
  • [ ] Trace được Variable Resolution từ current environment ra outer environment.

Nếu chưa chắc, quay lại Lesson 1.4.1 và Module 1.1–1.2 trước.

3. Learning Objectives (Mục tiêu học tập) ​

Sau bài này, bạn có thể:

  1. Phân biệt lifetime của Execution Context với lifetime của environment được closure giữ lại.
  2. Giải thích tại sao outer function đã return nhưng captured binding vẫn còn reachable.
  3. Vẽ reference/lifetime diagram cho một closure đơn giản.
  4. Dự đoán khi nào hai closures cùng giữ một environment và khi nào chúng giữ environment khác nhau.
  5. Debug một tình huống trong đó dữ liệu vẫn còn reachable vì một closure vẫn đang được giữ reference.
  6. Nhận biết điều kiện conceptual để environment có thể không còn cần được giữ lại.

4. Mental Model (Mô hình tư duy) ​

Mental Model — Closure Lifetime

Execution Context và Lexical Environment không nên được xem là cùng một thứ có cùng lifetime.

Khi outer function kết thúc, Execution Context của nó rời Call Stack. Nhưng nếu một function còn reachable vẫn cần bindings từ outer lexical environment, environment cần thiết đó vẫn còn reachable thông qua closure.

Flow:

text
outer() called
      ↓
Execution Context + Environment created
      ↓
inner function created
      ↓
inner keeps needed outer environment reference
      ↓
outer() returns inner
      ↓
Outer Execution Context leaves Call Stack
      ↓
Returned inner is still reachable
      ↓
Needed outer environment remains reachable

Điểm quan trọng:

Function execution kết thúc không đồng nghĩa mọi dữ liệu liên quan tới invocation đó lập tức biến mất.

Ví dụ:

js
function createReader() {
  const value = 42;

  return function read() {
    return value;
  };
}

const reader = createReader();

Sau khi createReader() return:

text
Global Environment
└── reader
     ↓
   Function read
     ↓
   Outer Environment
   └── value = 42

createReader không còn đang chạy. Nhưng reader vẫn reachable từ global binding, và function read vẫn cần value. Vì vậy environment chứa binding cần thiết vẫn còn reachable.

Common Misconception

Không nên nói:

"Closure làm outer function sống mãi."

Outer function không còn đang execute. Thứ được giữ lại là environment/bindings cần thiết mà closure vẫn có thể reach.

Cũng không nên kết luận engine bắt buộc phải giữ nguyên toàn bộ environment theo đúng hình vẽ. Engine có thể tối ưu implementation. Diagram ở đây là mental model để reasoning về observable behavior.

5. Core Concepts (Các khái niệm cốt lõi) ​

Essential (Bắt buộc) ​

  • Execution Lifetime: Khoảng thời gian một Function Execution Context đang tồn tại trên Call Stack.
  • Environment Reachability: Environment vẫn có thể được truy cập qua một chain/reference còn reachable.
  • Retained Environment: Outer environment vẫn cần tồn tại ở mức observable behavior vì closure còn cần bindings của nó.
  • Closure Lifetime: Gắn với việc function object còn reachable và còn giữ reference cần thiết đến outer environment.
  • Reference Chain: Một chuỗi reference có thể khiến function và environment tiếp tục reachable.

Supporting (Hỗ trợ) ​

  • Return một function có thể làm function đó escape khỏi outer invocation.
  • Gán closure vào variable, object hoặc collection có thể giữ closure reachable.
  • Nhiều closures có thể cùng tham chiếu đến một outer environment.
  • Khi không còn reference reachable tới closure, environment mà chỉ closure đó giữ có thể trở nên unreachable.

Awareness (Biết tồn tại) ​

  • JavaScript engine có Garbage Collector xử lý memory không còn reachable.
  • Engine có thể tối ưu cách lưu captured bindings.
  • "Không còn reachable" không có nghĩa bạn có thể dự đoán chính xác thời điểm memory được thu hồi.

6. Worked Example (Ví dụ phân tích từng bước) ​

Worked Example — Execution kết thúc nhưng Environment vẫn reachable:

js
function createAccount(name) {
  const createdAt = Date.now();

  function describe() {
    return `${name} - ${createdAt}`;
  }

  return describe;
}

const accountInfo = createAccount("Alice");
console.log(accountInfo());

Step 1 — createAccount("Alice") được gọi:

  • Tạo Function Execution Context cho createAccount.
  • Tạo lexical environment chứa:
    • name → "Alice"
    • createdAt → <timestamp>
    • binding cho describe
  • Function describe được tạo trong lexical environment này.

Step 2 — return describe:

  • Function object describe được trả về.
  • Global binding accountInfo giữ reference đến function đó.
  • describe cần resolve name và createdAt từ outer environment.

Step 3 — createAccount kết thúc:

  • Execution Context của createAccount bị pop khỏi Call Stack.
  • Không còn statement nào của createAccount đang execute.
  • Nhưng environment chứa name và createdAt vẫn cần reachable vì accountInfo còn giữ function describe.

Step 4 — accountInfo() được gọi sau đó:

  • Tạo Execution Context mới cho describe.
  • describe cần name và createdAt.
  • Variable Resolution đi qua outer environment reference.
  • Tìm thấy các bindings thuộc invocation cũ của createAccount.

Step 5 — Kết luận:

text
createAccount execution
        ↓
      ended

accountInfo reference
        ↓
describe function
        ↓
outer environment
        ↓
name + createdAt

Điều tồn tại sau return không phải "Execution Context cũ vẫn nằm trên stack". Thứ còn reachable là environment mà closure vẫn cần.

7. Prediction Exercise (Bài tập dự đoán) ​

Đừng chạy code. Dự đoán output và giải thích bằng reference + environment lifetime.

Prediction 1

js
function outer() {
  let value = 1;

  return function () {
    value = value + 1;
    return value;
  };
}

const fn = outer();

console.log(fn());
console.log(fn());

Prediction 2

js
function outer() {
  const value = Math.random();

  return function () {
    return value;
  };
}

const a = outer();
const b = outer();

console.log(a() === b());

Prediction 3

js
function outer() {
  const value = 10;

  return {
    readA() {
      return value;
    },
    readB() {
      return value * 2;
    },
  };
}

const api = outer();

console.log(api.readA());
console.log(api.readB());
[Đáp án & Giải thích]
  • Prediction 1:

    • Output: 2 rồi 3.
    • Giải thích: fn vẫn reachable và giữ reference cần thiết đến environment của một invocation outer. Binding value trong environment đó tiếp tục được đọc và cập nhật qua nhiều lần gọi fn.
  • Prediction 2:

    • Kết quả thông thường là false vì hai lần gọi outer() tạo hai invocation và hai environment khác nhau, mỗi environment giữ một kết quả Math.random() riêng.
    • Điểm cần reasoning: a không giữ environment của b, và ngược lại.
  • Prediction 3:

    • Output: 10 rồi 20.
    • readA và readB được tạo trong cùng một invocation outer, nên cả hai cùng có thể resolve binding value của cùng outer environment.

Transfer Check — reference bị giữ ở nơi khác:

js
const handlers = [];

function register() {
  const id = "user-42";

  function handler() {
    return id;
  }

  handlers.push(handler);
}

register();

Không cần biết một framework hay event system nào. Chỉ dùng reference model và trả lời:

  1. Sau khi register() return, handler còn reachable không?
  2. Reference nào đang giữ handler?
  3. id còn cần reachable không?
  4. Nếu handlers không còn giữ handler và không có reference nào khác tới nó, điều gì có thể xảy ra với environment chỉ được closure đó giữ?
Gợi ý trả lời
  1. Có. handler vẫn reachable.
  2. Array handlers giữ reference đến function handler.
  3. Có. handler vẫn có thể được gọi và cần resolve id.
  4. Environment có thể trở nên unreachable nếu thực sự không còn reference chain nào cần nó. Thời điểm memory được reclaim là implementation detail của Garbage Collector và không thuộc phạm vi lesson này.

8. Implementation Lab (Bài lab thực hành) ​

Lab 1 — Guided: Quan sát lifetime bằng API đơn giản ​

Bước 0 — Vẽ diagram trước khi code.

Với code:

js
const session = createSession("Alice");

Hãy vẽ:

text
session
  ↓
returned function
  ↓
outer environment
  ↓
name

Sau đó implement:

js
function createSession(name) {
  // TODO
}

const session = createSession("Alice");

console.log(session()); // "Session: Alice"
console.log(session()); // "Session: Alice"

Yêu cầu:

  • createSession phải return một function.
  • Function trả về phải đọc name từ outer environment.
  • Không copy name sang global variable.
[Đáp án tham khảo]
js
function createSession(name) {
  return function () {
    return `Session: ${name}`;
  };
}
  • Giải thích: Sau khi createSession kết thúc, returned function vẫn reachable qua session. Function đó vẫn cần binding name, nên environment cần thiết của invocation vẫn reachable.

Lab 2 — Partial Scaffold: Hai closures cùng một lifetime source ​

Hoàn thiện:

js
function createState(initialValue) {
  let value = initialValue;

  return {
    read() {
      // TODO
    },
    write(nextValue) {
      // TODO
    },
  };
}

const state = createState(10);

console.log(state.read()); // 10
state.write(20);
console.log(state.read()); // 20

Sau khi implement, trả lời:

  1. read và write có giữ hai value khác nhau không?
  2. Khi createState return, vì sao value vẫn accessible?
  3. Nếu chỉ còn read reachable mà write không còn được giữ, value vẫn cần tồn tại không?
[Đáp án tham khảo]
js
function createState(initialValue) {
  let value = initialValue;

  return {
    read() {
      return value;
    },
    write(nextValue) {
      value = nextValue;
    },
  };
}
  • read và write cùng được tạo trong một invocation nên cùng resolve một binding value.
  • Chỉ cần một closure còn reachable và còn cần value, binding đó vẫn cần observable.
  • Nếu read vẫn reachable và vẫn dùng value, environment chứa value vẫn cần reachable.

Lab 3 — Independent: Release một reference ​

Tạo:

js
function createReader(value) {
  // implement
}

let reader = createReader("secret");

console.log(reader()); // "secret"

reader = null;

Viết một đoạn giải thích 4–6 câu, không cần đo memory:

  • Trước reader = null, reference chain nào giữ returned function reachable?
  • Returned function giữ outer binding nào?
  • Sau reader = null, bạn có được phép khẳng định memory bị giải phóng ngay lập tức không?
  • Câu kết luận an toàn hơn là gì?
[Đáp án tham khảo]
js
function createReader(value) {
  return function () {
    return value;
  };
}

Trước reader = null, global binding reader giữ reference đến returned function. Function đó cần binding value của outer environment. Sau reader = null, nếu không còn reference nào khác đến function thì reference chain này không còn từ reader. Ta không khẳng định memory được giải phóng ngay lập tức. Kết luận an toàn là function/environment có thể trở nên unreachable và việc reclaim memory thuộc Garbage Collector implementation.

9. Edge Cases (Các trường hợp ngoại lệ) ​

Edge Case 1 — Outer return không có nghĩa mọi environment đều phải giữ ​

js
function outer() {
  const x = 10;

  return function inner() {
    return "hello";
  };
}

const fn = outer();

Phân tích

inner không cần x để tạo observable behavior. Mental model không nên biến thành "cứ return inner function thì toàn bộ outer environment bắt buộc được giữ nguyên mãi". Engine có thể tối ưu những bindings không cần thiết. Điều learner phải theo dõi là function cần resolve binding nào.

Edge Case 2 — Hai closures cùng giữ một environment ​

js
function outer() {
  let value = 0;

  return [
    () => value,
    () => {
      value = value + 1;
    },
  ];
}

const [read, increment] = outer();

Phân tích

read và increment đến từ cùng một invocation và đều dùng value. Chúng cùng giữ khả năng truy cập một binding value. Việc bỏ reference đến một closure chưa đủ để kết luận environment không còn cần thiết nếu closure còn lại vẫn reachable.

Edge Case 3 — Closure tồn tại không đồng nghĩa memory leak ​

js
function makeFormatter(prefix) {
  return (text) => `${prefix}: ${text}`;
}

const formatError = makeFormatter("ERROR");

Phân tích

formatError giữ prefix là behavior có chủ đích. Đây là closure hoạt động đúng, không phải tự động là "memory leak". Leak là vấn đề ownership/reachability không mong muốn và sẽ được học sâu hơn ở Stage 11.

10. Debug Lab (Bài lab gỡ lỗi) ​

Debug Lab — Dữ liệu vẫn reachable dù setup function đã return

Một developer nghĩ rằng local data sẽ "biến mất" ngay khi registerHandler() kết thúc.

Symptom (Triệu chứng): Sau khi registerHandler() return, callback lưu trong handlers vẫn đọc được user.name.

Reproduction (Tái hiện lỗi):

js
const handlers = [];

function registerHandler() {
  const user = { name: "Alice" };

  handlers.push(function () {
    return user.name;
  });
}

registerHandler();

console.log(handlers[0]()); // "Alice"

Evidence (Bằng chứng):

  • registerHandler() đã kết thúc.
  • Function Execution Context của registerHandler không còn trên Call Stack.
  • handlers[0] vẫn là một function reachable từ global array.
  • Function đó cần resolve binding user.

Hypothesis (Giả thuyết):

  • Callback trong handlers giữ reference cần thiết đến outer environment của registerHandler.
  • Vì callback vẫn reachable, binding user vẫn cần reachable.

Verification (Xác minh):

js
const callback = handlers[0];

console.log(callback()); // "Alice"

Ngay cả khi gọi callback ở vị trí khác, nó vẫn resolve được user. Điều này phù hợp với lexical environment reference, không phụ thuộc nơi callback được gọi.

Root Cause (Nguyên nhân gốc rễ):

Không có bug trong Closure. Bug nằm ở mental model của developer:

"Function return → mọi local data bị xóa ngay."

Mental model đúng:

"Execution Context kết thúc, nhưng environment cần bởi closure còn reachable có thể tiếp tục reachable."

Fix (Sửa):

Nếu callback không còn cần thiết, code phải ngừng giữ reference đến callback theo lifecycle của hệ thống:

js
handlers.length = 0;

Trong ví dụ toy này, thao tác trên loại bỏ references từ array tới callbacks.

Prevention (Phòng ngừa):

  • Khi closure được lưu vào array/object/registry, xác định rõ ai đang giữ reference.
  • Phân biệt "function đã return" với "closure đã unreachable".
  • Với lifecycle dài, luôn hỏi: "Reference nào sẽ release callback này?"
  • Không dùng từ "memory leak" chỉ vì thấy closure giữ data; cần phân biệt retention có chủ đích và retention ngoài ý muốn.

11. Design Exercise (Bài tập thiết kế giải pháp) ​

Không áp dụng Design Exercise đầy đủ ở depth L4.

Ở lesson này, yêu cầu dừng ở lifecycle reasoning:

text
Who creates the closure?
        ↓
Who keeps the closure?
        ↓
What outer bindings does it need?
        ↓
When should the holder release it?

Design API private state sẽ được thực hành ở Lesson 1.4.3 và Factory Functions ở Lesson 1.4.4.

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

Production Scenario — Callback Registry

Một module giữ danh sách callback:

js
const callbacks = [];

function setupFeature(config) {
  function onRun() {
    return config.mode;
  }

  callbacks.push(onRun);
}

Code gọi:

js
setupFeature({
  mode: "production",
});

setupFeature() kết thúc ngay sau đó. Tuy nhiên callbacks vẫn giữ onRun.

Context: Callback cần sử dụng config.mode ở một thời điểm sau khi setup đã hoàn tất.

Constraint: Không được giả định local bindings biến mất chỉ vì setup function đã return.

Behavior:onRun vẫn có thể đọc config vì callback còn reachable từ registry và closure vẫn giữ reference cần thiết đến outer environment.

Engineering Question: Khi feature bị disable, registry có tiếp tục giữ callback không?

Nếu lifecycle của hệ thống yêu cầu callback không còn được sử dụng, reference holder phải có cơ chế loại bỏ callback. Lesson này chưa đi vào memory profiling hay GC internals; mục tiêu là nhận ra lifetime của data bị ảnh hưởng bởi lifetime của references giữ closure.

Code Review Lens

Khi thấy callback được lưu vào collection có lifetime dài, hãy hỏi:

"Ai sở hữu callback này, và lúc nào reference đến nó được release?"

Đây là câu hỏi lifecycle, không chỉ là câu hỏi syntax Closure.

13. AI-assisted Exercise (Bài tập với AI) ​

Level B — Challenge (Thách thức)

  1. Tự trả lời trước: Viết 5 câu giải thích khác nhau giữa "Execution Context đã kết thúc" và "Outer Environment vẫn reachable".
  2. Hỏi AI: Dùng prompt: "When a JavaScript outer function returns but a closure still uses its variables, what exactly remains reachable? Is the old execution context still on the call stack?"
  3. So sánh: Kiểm tra xem AI có nói execution context "vẫn sống trên stack" hay closure "copy toàn bộ variables" không.
  4. Challenge: Hỏi tiếp: "If the last reachable reference to the closure is removed, can we guarantee memory is immediately freed?"
  5. Verify: Đối chiếu với mental model Lexical Environment + references; không yêu cầu AI giải thích GC internals.

Gợi ý

Câu trả lời đạt yêu cầu phải phân biệt được:

  • Execution Context rời Call Stack.
  • Closure function vẫn có thể reachable.
  • Needed outer environment/bindings vẫn có thể reachable.
  • Không thể suy ra thời điểm Garbage Collector reclaim memory.
Đáp án tham khảo
  • Bạn nghĩ:

    • Khi outer function return, execution của invocation đó đã kết thúc.
    • Execution Context không tiếp tục nằm trên Call Stack.
    • Nếu returned closure còn reachable, closure vẫn giữ reference cần thiết đến lexical environment.
    • Vì vậy captured bindings vẫn có thể được resolve ở lần gọi sau.
    • Nếu closure không còn reachable và không còn reference chain khác, environment có thể trở nên unreachable.
  • AI có thể nói sai hoặc mơ hồ:

    • "JavaScript giữ execution context lại trong memory."
    • "Closure copy toàn bộ local variables."
    • "Khi set closure = null thì memory được free ngay."
  • Điểm cần sửa:

    • Không đồng nhất Execution Context với Lexical Environment.
    • Không biến mental model reference thành copy.
    • Không hứa về thời điểm GC.

14. Teach Back (Dạy lại) ​

Giải thích cho một đồng nghiệp junior trong 2 phút:

"Outer function đã return rồi. Tại sao closure vẫn truy cập được local variable, và environment đó tồn tại đến bao lâu?"

Yêu cầu:

  • Dùng đúng terminology: Execution Context, Call Stack, Lexical Environment, reference, reachable.
  • Không dùng câu "vì JavaScript nhớ" làm giải thích cuối cùng.
  • Không nói Execution Context cũ vẫn nằm trên stack.
  • Không khẳng định thời điểm Garbage Collector giải phóng memory.
Mô phỏng
  • Bạn nói:
    • Khi outer function được gọi, engine tạo Execution Context và lexical environment cho invocation đó.
    • Inner function được tạo trong lexical environment này và có outer environment reference cần thiết để resolve các bindings bên ngoài.
    • Khi outer function return, Execution Context của nó bị pop khỏi Call Stack. Outer function không còn đang execute.
    • Tuy nhiên, nếu inner function được return và vẫn reachable qua một variable, object hoặc collection, function đó vẫn có thể cần outer bindings.
    • Vì thế environment cần thiết vẫn reachable thông qua closure. Khi inner function được gọi sau này, Variable Resolution vẫn có thể tìm thấy các bindings đó.
    • Nếu không còn reference reachable tới closure và không còn chain nào khác giữ environment, environment có thể trở nên unreachable. Nhưng JavaScript code không nên khẳng định chính xác thời điểm memory được thu hồi.

💡 Hình dung Call Stack là bàn làm việc, còn environment là hồ sơ. Outer function rời bàn làm việc, nhưng hồ sơ chưa thể cất đi nếu returned function vẫn đang giữ chìa khóa để tra cứu nó.

Gợi ý đánh giá bản thân

  • Bạn có phân biệt được "không còn execute" với "không còn reachable" không?
  • Bạn có vẽ được reference chain giữ environment tồn tại không?
  • Bạn có giải thích được tại sao bỏ một closure chưa chắc đủ nếu closure khác vẫn giữ cùng environment không?
  • Bạn có tránh được kết luận "closure = memory leak" không?

15. Assessment (Đánh giá) ​

Objective (Mục tiêu)Hình thức đánh giá
Phân biệt execution lifetime và environment lifetimeTeach Back — phân biệt đúng Context / Environment
Giải thích environment retentionWorked Example — trace reference chain
Vẽ closure lifetime diagramLab 1 — diagram đúng từ holder → function → environment
Dự đoán closure lifetime behaviorPrediction — 3/3 đúng
Nhận biết shared retained environmentEdge Case 2
Debug retained dataDebug Lab — tìm đúng reference holder
Nhận biết giới hạn của mental modelEdge Case 1 + AI Exercise — không suy diễn GC implementation

16. Exit Criteria (Tiêu chí qua bài) ​

  • [ ] Có thể giải thích sự khác nhau giữa Execution Context lifetime và Lexical Environment reachability.
  • [ ] Có thể giải thích tại sao outer return nhưng closure vẫn resolve captured binding.
  • [ ] Có thể vẽ reference chain holder → closure → outer environment.
  • [ ] Dự đoán đúng 3/3 prediction scenarios.
  • [ ] Có thể giải thích tại sao hai closures trong cùng invocation có thể cùng giữ một environment.
  • [ ] Có thể debug một case data vẫn reachable vì callback còn được giữ trong collection.
  • [ ] Không nói "closure copy variable" như một technical fact.
  • [ ] Không nói "outer execution context vẫn nằm trên stack" sau khi outer return.
  • [ ] Không kết luận "closure = memory leak".
  • [ ] Biết rằng khi reference chain biến mất, environment có thể trở nên unreachable nhưng thời điểm memory reclaim là implementation detail.

17. Spiral Connection (Liên kết xoắn ốc) ​

Previous (Trước): Lesson 1.4.1 — Closure Formation. Bạn đã học inner function giữ outer environment reference và dùng reference đó để resolve captured variables.

Current (Hiện tại): Closure và Lifetime. Bạn tách rõ Execution Context đã kết thúc khỏi environment vẫn còn reachable vì closure còn được giữ reference.

Next (Tiếp theo):

  • Lesson 1.4.3 — Closure làm Private State (dùng lifetime của environment để duy trì state có chủ đích)
  • Lesson 1.4.4 — Factory Functions (mỗi invocation tạo state/lifetime riêng)
  • Lesson 1.4.6–1.4.7 — Callback + Async (closure tồn tại qua thời gian)
  • Stage 8 — React Stale Closure (lifetime + captured state trong rendering)
  • Stage 11 — Memory & Debugging (reachability, retention, profiling và leak diagnosis)
📴 Offline Mode — Content served from cache