Skip to content

Lesson 1.4.4 — Factory Functions ​

Bài 1.4.4 — Factory Functions
Factories, Independent State & Closure-based Objects • 32 phút
0:00 / 0:00

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

FieldValue
Stage1
Module1.4 — Closures
Lesson1.4.4 — Factory Functions
CompetencyC02 — JavaScript Runtime (C02.5 Closure)
Depth TargetL5 (Implement)
PrerequisitesClosure Formation (1.4.1), Closure Lifetime (1.4.2), Private State (1.4.3), Lexical Environment (1.2.7), Variable Resolution (1.2.6)
Estimated Cognitive LoadHigh

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

  • Constructor function, new, prototype chain (Stage 2)
  • class và object-oriented design (Stage 2)
  • Async callbacks và scheduling (Stage 3)
  • React state / stale closure (Stage 8)
  • Garbage Collection internals và memory profiling (Stage 11)

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

Ở Lesson 1.4.3, bạn đã dùng closure để tạo một createCounter() có private state. Nhưng production code hiếm khi chỉ cần đúng một counter. Ta thường cần một cách tạo ra nhiều instance có cùng behavior nhưng state độc lập:

js
const userA = createUser("Alice");
const userB = createUser("Bob");

Cả userA và userB được tạo từ cùng một function. Vậy tại sao thay đổi state của userA không làm state của userB thay đổi?

Đó là vấn đề Factory Functions giải quyết trong module này: dùng mỗi invocation của factory để tạo một environment riêng, rồi trả về các function cùng truy cập state của environment đó.

Nếu không hiểu cơ chế này, bạn sẽ dễ:

  • Đặt state sai scope và vô tình chia sẻ giữa nhiều instance.
  • Nghĩ rằng object mới đồng nghĩa với state mới.
  • Không giải thích được tại sao hai method trong cùng instance chia sẻ private state.
  • Debug sai khi nhiều instance ảnh hưởng lẫn nhau.
  • Dùng factory như một pattern thuộc lòng thay vì hiểu nó từ closure.

Terminology

Factory function là một function tạo và trả về một value/object theo một contract. Factory không bắt buộc phải dùng closure.

Trong lesson này, ta tập trung vào closure-based factory vì mục tiêu của Module 1.4 là hiểu Closure.

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 inner function giữ reference đến lexical environment nơi nó được tạo.
  • [ ] Phân biệt execution context bị pop với lexical environment còn được closure giữ lại.
  • [ ] Biết mỗi lần gọi một function tạo ra một invocation riêng.
  • [ ] Implement được private state bằng closure như createCounter().
  • [ ] Phân biệt local binding với binding ở outer/global scope.

Nếu thiếu, quay lại Lesson 1.4.1–1.4.3 trước.

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

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

  1. Giải thích tại sao mỗi lần gọi factory có thể tạo một state độc lập.
  2. Vẽ environment diagram cho hai instance được tạo từ cùng một factory.
  3. Dự đoán behavior khi state nằm trong factory và khi state vô tình nằm ngoài factory.
  4. Implement createUser(), createLogger() và createStore() bằng closure.
  5. Debug bug shared state giữa nhiều factory instance.
  6. Thiết kế một factory API nhỏ giữ state private và bảo vệ invariant.

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

Mental Model — Factory Functions

Mỗi lần factory được gọi, một invocation mới được tạo. Invocation đó có lexical environment và local bindings riêng. Các function được tạo bên trong invocation có thể giữ reference đến environment đó.

Vì vậy:

cùng một factory → nhiều invocation → nhiều environment → state có thể độc lập.

Ví dụ:

js
function createCounter() {
  let count = 0;

  return {
    increment() {
      count = count + 1;
    },
    getValue() {
      return count;
    },
  };
}

const a = createCounter();
const b = createCounter();

Mental model:

text
createCounter() — Invocation A
        ↓
Environment A
└── count = 0
    ↑
    ├── a.increment
    └── a.getValue

createCounter() — Invocation B
        ↓
Environment B
└── count = 0
    ↑
    ├── b.increment
    └── b.getValue

a.increment và a.getValue cùng tham chiếu đến Environment A. b.increment và b.getValue cùng tham chiếu đến Environment B.

Điểm quan trọng: object mới không phải nguyên nhân cốt lõi tạo state độc lập. State độc lập đến từ việc mỗi factory invocation có local bindings riêng và closures của instance giữ đúng environment đó.

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

Essential (Bắt buộc) ​

  • Factory Function: Function tạo và trả về một value/object theo một contract.
  • Per-invocation Environment: Mỗi lần gọi factory tạo một invocation với local bindings riêng.
  • Per-instance State: State thuộc environment của một factory invocation cụ thể.
  • Shared Private State: Nhiều method được tạo trong cùng invocation có thể cùng truy cập một private binding.
  • Instance Isolation: Hai instance từ hai invocation khác nhau không chia sẻ local state nếu state được khai báo bên trong factory.

Supporting (Hỗ trợ) ​

  • Object literal có thể làm container cho public API.
  • Parameters của factory có thể trở thành captured bindings.
  • Closure cho phép API còn truy cập state sau khi factory đã return.
  • State ownership phụ thuộc vị trí declaration của binding.

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

  • Factory có thể được implement theo nhiều cách, không chỉ closure.
  • Prototype/class có cách tổ chức behavior khác và sẽ học ở Stage 2.
  • Closure-based factory có trade-off về allocation/memory nhưng chưa phân tích ở Stage 1.

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

Worked Example — Trace hai instance từ cùng một factory:

js
function createUser(name) {
  let loginCount = 0;

  return {
    login() {
      loginCount = loginCount + 1;
      return `${name}: ${loginCount}`;
    },
    getLoginCount() {
      return loginCount;
    },
  };
}

const alice = createUser("Alice");
const bob = createUser("Bob");

alice.login();
alice.login();
bob.login();

console.log(alice.getLoginCount());
console.log(bob.getLoginCount());

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

  • Tạo một invocation mới của createUser.
  • Environment A chứa name → "Alice" và loginCount → 0.
  • login và getLoginCount được tạo trong Environment A.
  • Hai method đều giữ reference cần thiết đến Environment A.
  • Object chứa hai method được trả về và gán cho alice.

Step 2 — createUser("Bob") được gọi:

  • Đây là invocation khác, không phải dùng lại invocation trước.
  • Environment B chứa name → "Bob" và loginCount → 0.
  • Hai method mới được tạo trong Environment B.
  • Object mới được trả về và gán cho bob.

Step 3 — alice.login() chạy hai lần:

  • alice.login resolve loginCount trong Environment A.
  • Lần 1: loginCount_A từ 0 → 1.
  • Lần 2: loginCount_A từ 1 → 2.
  • Environment B không bị truy cập.

Step 4 — bob.login() chạy một lần:

  • bob.login resolve loginCount trong Environment B.
  • loginCount_B từ 0 → 1.
  • Environment A không thay đổi.

Step 5 — Kết quả:

text
2
1

Hai instance độc lập vì chúng được tạo từ hai invocation khác nhau, mỗi invocation có environment và bindings riêng.

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 invocation + environment + binding.

Prediction 1

js
function createCounter(start) {
  let count = start;

  return {
    increment() {
      count = count + 1;
    },
    getValue() {
      return count;
    },
  };
}

const a = createCounter(0);
const b = createCounter(10);

a.increment();
a.increment();
b.increment();

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

Prediction 2

js
let count = 0;

function createCounter() {
  return {
    increment() {
      count = count + 1;
    },
    getValue() {
      return count;
    },
  };
}

const a = createCounter();
const b = createCounter();

a.increment();
a.increment();
b.increment();

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

Prediction 3

js
function createBox(value) {
  return {
    set(nextValue) {
      value = nextValue;
    },
    get() {
      return value;
    },
  };
}

const a = createBox("A");
const b = createBox("B");

a.set("A2");

console.log(a.get());
console.log(b.get());
[Đáp án & Giải thích]
  • Prediction 1:

    • Output: 2 rồi 11.
    • Giải thích: a và b đến từ hai invocation khác nhau. Mỗi invocation tạo một binding count riêng. Hai lần a.increment() chỉ thay đổi count của Environment A; b.increment() chỉ thay đổi count của Environment B.
  • Prediction 2:

    • Output: 3 rồi 3.
    • Giải thích: count được khai báo bên ngoài factory. Hai invocation tạo ra các method khác nhau, nhưng các method đều resolve cùng một outer binding count. Object khác nhau không làm binding bên ngoài tự tách thành hai state.
  • Prediction 3:

    • Output: "A2" rồi "B".
    • Giải thích: Mỗi invocation của createBox có parameter binding value riêng. set và get của a cùng dùng Environment A; set và get của b cùng dùng Environment B.

Transfer Check — không cần biết pattern này trước:

js
function createToggle(initialState) {
  let active = initialState;

  return {
    toggle() {
      active = !active;
    },
    isActive() {
      return active;
    },
  };
}

const menu = createToggle(false);
const modal = createToggle(false);

menu.toggle();

console.log(menu.isActive());
console.log(modal.isActive());

Hãy trả lời:

  1. Có bao nhiêu factory invocation?
  2. Có bao nhiêu binding active?
  3. menu.toggle() thay đổi binding nào?
  4. Output cuối cùng là gì?
Gợi ý trả lời
  1. Có hai invocation của createToggle.
  2. Có hai binding active, mỗi binding thuộc một invocation.
  3. menu.toggle() thay đổi active trong environment được closures của menu giữ lại.
  4. Output là true rồi false.

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

Lab 1 — Guided: createUser() ​

Bước 0 — Vẽ diagram trước khi code. Với createUser("Alice"), hãy xác định:

  • Environment nào được tạo?
  • Binding nào là private state?
  • Method nào đọc state?
  • Method nào thay đổi state?
  • Nếu gọi createUser("Bob"), binding nào được tạo thêm?

Implement:

js
function createUser(initialName) {
  // TODO
}

const user = createUser("Alice");

console.log(user.getName()); // Alice
user.rename("Alicia");
console.log(user.getName()); // Alicia

Yêu cầu:

  • name phải private.
  • Có getName() và rename(nextName).
  • Không lưu name bằng property public như user.name.
  • Hai user phải có state độc lập.
[Đáp án tham khảo]
js
function createUser(initialName) {
  let name = initialName;

  return {
    getName() {
      return name;
    },
    rename(nextName) {
      name = nextName;
    },
  };
}
  • Giải thích: Mỗi invocation của createUser tạo một binding name riêng. getName và rename của cùng instance cùng tham chiếu đến binding đó.

Lab 2 — Partial Scaffold: createLogger() ​

Hoàn thiện factory sau:

js
function createLogger(prefix) {
  let count = 0;

  return {
    log(message) {
      // TODO: tăng count và log `[PREFIX] #COUNT message`
    },
    getCount() {
      // TODO
    },
  };
}

const apiLogger = createLogger("API");

apiLogger.log("start"); // [API] #1 start
apiLogger.log("done"); // [API] #2 done

console.log(apiLogger.getCount()); // 2

Sau đó tạo thêm:

js
const authLogger = createLogger("AUTH");
authLogger.log("login");

console.log(authLogger.getCount()); // 1
console.log(apiLogger.getCount()); // 2
[Đáp án tham khảo]
js
function createLogger(prefix) {
  let count = 0;

  return {
    log(message) {
      count = count + 1;
      console.log(`[${prefix}] #${count} ${message}`);
    },
    getCount() {
      return count;
    },
  };
}
  • Giải thích: prefix và count là bindings của từng factory invocation. Mỗi logger có environment riêng nên prefix và counter không ảnh hưởng logger khác.

Lab 3 — Independent: createStore() ​

Implement một store nhỏ:

js
const store = createStore(10);

console.log(store.get()); // 10

store.set(20);
console.log(store.get()); // 20

store.update((value) => value + 5);
console.log(store.get()); // 25

Contract:

text
get()
set(nextValue)
update(updater)

Constraints:

  • state phải private.
  • Không dùng global mutable state.
  • Không expose state bằng property public.
  • update phải nhận state hiện tại và lưu kết quả mới.
  • Hai store phải giữ state độc lập.
[Đáp án tham khảo]
js
function createStore(initialState) {
  let state = initialState;

  return {
    get() {
      return state;
    },
    set(nextState) {
      state = nextState;
    },
    update(updater) {
      state = updater(state);
    },
  };
}
  • Giải thích: get, set và update được tạo trong cùng factory invocation nên cùng truy cập một binding state. Một invocation khác tạo ra một binding state khác.

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

Edge Case 1 — Object khác nhau nhưng state vẫn shared ​

js
let count = 0;

function createCounter() {
  return {
    increment() {
      count = count + 1;
    },
    getValue() {
      return count;
    },
  };
}

Phân tích

createCounter() vẫn trả về object mới mỗi lần gọi, nhưng count không nằm trong factory. Tất cả methods đều resolve cùng một binding bên ngoài. Đây là counterexample trực tiếp cho câu: "object mới thì state tự động độc lập."

Edge Case 2 — Binding private nhưng object reference bị expose ​

js
function createProfile() {
  const profile = { name: "Alice" };

  return {
    getProfile() {
      return profile;
    },
  };
}

const user = createProfile();
const profile = user.getProfile();

profile.name = "Changed";
console.log(user.getProfile().name);

Phân tích

Binding profile vẫn không truy cập trực tiếp được từ bên ngoài, nhưng getProfile() trả ra chính object reference. Caller có thể mutate object đó. Closure làm binding private không đồng nghĩa với object được trả ra ngoài là immutable.

Edge Case 3 — Parameter binding là state của invocation ​

js
function createReader(value) {
  return function () {
    return value;
  };
}

let source = 1;
const read = createReader(source);

source = 2;

console.log(read());

Phân tích

Output là 1. Khi gọi createReader(source), giá trị hiện tại của source được truyền vào parameter binding value. Sau đó source và value là hai bindings khác nhau. Closure của read tham chiếu đến value, không tham chiếu đến biến source ở caller.

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

Debug Lab — Factory instances vô tình chia sẻ state

Symptom (Triệu chứng): Developer mong đợi a có giá trị 2 và b có giá trị 1, nhưng cả hai đều trả về 3.

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

js
let count = 0;

function createCounter() {
  return {
    increment() {
      count = count + 1;
    },
    getValue() {
      return count;
    },
  };
}

const a = createCounter();
const b = createCounter();

a.increment();
a.increment();
b.increment();

console.log(a.getValue()); // 3
console.log(b.getValue()); // 3

Evidence (Bằng chứng):

  • createCounter() được gọi hai lần.
  • Hai object khác nhau được tạo.
  • Nhưng chỉ có một declaration let count = 0.
  • Declaration đó nằm ngoài createCounter.

Hypothesis (Giả thuyết):

  • Hai nhóm methods đều resolve cùng một outer binding count.
  • Vì vậy update từ a và b cộng dồn vào cùng state.

Verification (Xác minh): Tạo instance thứ ba và chỉ gọi một lần:

js
const c = createCounter();
c.increment();

console.log(a.getValue());
console.log(b.getValue());
console.log(c.getValue());

Nếu cả ba cùng trả về 4, giả thuyết shared binding được xác nhận.

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

  • State cần có ownership theo từng instance.
  • Nhưng count lại được khai báo ở scope dùng chung.
  • Factory tạo object mới, nhưng không tạo binding count mới.

Fix (Sửa):

js
function createCounter() {
  let count = 0;

  return {
    increment() {
      count = count + 1;
    },
    getValue() {
      return count;
    },
  };
}

Prevention (Phòng ngừa):

  • Khi review factory, xác định rõ mutable state nào là per-instance và state nào là shared.
  • Với per-instance state, kiểm tra declaration có nằm trong factory invocation không.
  • Test tối thiểu hai instance và interleave method calls.
  • Không dùng "object khác nhau" làm bằng chứng rằng state đã độc lập.

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

Design Exercise — createWallet(initialBalance)

Bạn cần API:

text
getBalance()
deposit(amount)
withdraw(amount)

Constraints:

  • balance phải private.
  • deposit(amount) chỉ nhận số dương.
  • withdraw(amount) không được làm balance âm.
  • Hai wallet phải độc lập.
  • Caller không được sửa balance trực tiếp.

Trước khi code, hãy trả lời:

  1. balance phải được khai báo ở scope nào?
  2. Những method nào cần truy cập cùng binding balance?
  3. Validation nên nằm trong method hay để caller tự làm?
  4. Vì sao không nên trả trực tiếp { balance } rồi cho caller tự mutate?
  5. Test nào chứng minh hai wallet không chia sẻ state?
[Một phương án tham khảo]
js
function createWallet(initialBalance) {
  let balance = initialBalance;

  return {
    getBalance() {
      return balance;
    },
    deposit(amount) {
      if (amount <= 0) return false;
      balance = balance + amount;
      return true;
    },
    withdraw(amount) {
      if (amount <= 0 || amount > balance) return false;
      balance = balance - amount;
      return true;
    },
  };
}
  • Decision: balance là per-instance state nên nằm trong factory.
  • Invariant: Mọi mutation đi qua deposit hoặc withdraw, nơi contract được kiểm tra.
  • Isolation: Mỗi invocation tạo một binding balance riêng.

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

Production Scenario — Logger theo subsystem

Một ứng dụng tạo logger cho từng subsystem:

js
const authLogger = createLogger("AUTH");
const paymentLogger = createLogger("PAYMENT");

Requirement:

  • Mỗi logger có prefix riêng.
  • Mỗi logger có counter riêng.
  • Log của AUTH không được làm counter của PAYMENT tăng.

Implementation ban đầu:

js
function createLogger(prefix) {
  let count = 0;

  return {
    log(message) {
      count = count + 1;
      console.log(`[${prefix}] #${count} ${message}`);
    },
  };
}

Sau một refactor, developer chuyển count ra ngoài để "dùng chung":

js
let count = 0;

function createLogger(prefix) {
  return {
    log(message) {
      count = count + 1;
      console.log(`[${prefix}] #${count} ${message}`);
    },
  };
}

Kết quả có thể thành:

text
[AUTH] #1 login
[PAYMENT] #2 create-order
[AUTH] #3 logout

Vấn đề: Refactor không chỉ đổi vị trí variable. Nó đã đổi state ownership từ per-instance thành shared.

Engineering Decision: Trước khi kéo mutable state ra scope rộng hơn để "reuse", phải hỏi:

State này thuộc lifetime của từng instance hay thật sự phải được chia sẻ giữa mọi instance?

Đây là ví dụ một thay đổi scope nhỏ nhưng làm thay đổi semantics của hệ thống.

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

Level C — Delegate & Inspect (Ủy quyền và kiểm tra)

  1. Tự trả lời trước: Viết 5–7 câu giải thích tại sao hai lần gọi createStore(0) tạo được hai state độc lập.
  2. Hỏi AI: Dùng prompt: "Explain why two instances created by the same JavaScript closure-based factory can hold independent state. Use invocation, lexical environment, binding, and closure."
  3. Inspect: Tìm xem AI có nói "closure copy value", "object tự có closure" hoặc "factory tự động tạo private state" không.
  4. Challenge: Đưa cho AI counterexample có state nằm ngoài factory và hỏi tại sao hai object lại share state.
  5. Verify: Kiểm tra lại mental model bằng knowledge từ Closure Formation, Lexical Environment và Variable Resolution.

Gợi ý

Câu trả lời tốt phải nối được chuỗi: factory invocation → environment mới → local binding mới → returned methods giữ reference → instance isolation.

Nếu AI chỉ nói "mỗi object có state riêng" mà không giải thích binding nằm ở đâu, câu trả lời chưa đủ depth.

Đáp án tham khảo
  • Bạn nghĩ:

    • Mỗi lần gọi createStore là một invocation riêng.
    • Mỗi invocation tạo local binding state riêng.
    • Các methods được tạo trong invocation đó giữ reference cần thiết đến lexical environment tương ứng.
    • Vì vậy methods của store A resolve state_A, còn methods của store B resolve state_B.
  • AI có thể trả lời:

    • "Factory tạo ra object mới và closure lưu một copy của state trong object đó."
  • Điểm cần challenge:

    • Object mới không đảm bảo state độc lập nếu state nằm ngoài factory.
    • Closure không nên được mô tả là "copy state".
    • State nằm trong lexical environment, không phải tự động "nằm trong object" chỉ vì object chứa methods.
  • Counterexample để kiểm tra:

js
let state = 0;

function createStore() {
  return {
    get: () => state,
    set: (value) => {
      state = value;
    },
  };
}
  • Kết luận:
    • Mental model đúng phải giải thích được cả happy path lẫn counterexample shared state.

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

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

"Tại sao const a = createStore(0) và const b = createStore(0) có thể giữ hai state độc lập dù cả hai được tạo từ cùng một function createStore?"

Yêu cầu:

  • Dùng đúng terminology: invocation, environment, binding, closure.
  • Không được trả lời chỉ bằng "vì factory tạo object mới".
  • Phải giải thích điều gì xảy ra nếu state được chuyển ra ngoài factory.
Mô phỏng
  • Bạn nói:
    • Khi createStore(0) được gọi lần đầu, đó là một invocation riêng và nó tạo một lexical environment chứa binding state.
    • Các method như get và set được tạo trong invocation đó nên giữ reference cần thiết đến environment này.
    • Khi gọi createStore(0) lần thứ hai, engine không dùng lại local binding state của lần đầu. Một invocation mới tạo một environment mới và một binding state mới.
    • Vì vậy methods của a truy cập state của invocation A, còn methods của b truy cập state của invocation B.
    • Object mới chỉ là public API được trả về. Nguyên nhân cốt lõi của state isolation là local bindings thuộc hai factory invocation khác nhau.
    • Nếu đưa state ra ngoài factory, các methods của cả a và b có thể resolve cùng một outer binding và bắt đầu ảnh hưởng lẫn nhau.

💡 Hình dung factory là một khuôn. Mỗi lần gọi khuôn tạo một ngăn state mới và các methods được trả ra giữ chìa khóa đến đúng ngăn của lần gọi đó.

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

  • Bạn có vẽ được hai environment song song cho a và b không?
  • Bạn có chỉ ra được binding nào thuộc invocation nào không?
  • Nếu di chuyển declaration state, bạn có dự đoán được sharing behavior thay đổi ra sao không?
  • Bạn có giải thích được tại sao "object mới" không phải bằng chứng đủ cho "state mới" không?

15. Assessment (Đánh giá) ​

Objective (Mục tiêu)Hình thức đánh giá
Giải thích per-instance stateTeach Back — đúng invocation → environment → binding
Vẽ factory environment diagramWorked Example + Lab 1 — hai environment độc lập
Dự đoán behavior của factoryPrediction — 3/3 đúng
Implement closure-based factoryLab 1–3 — createUser, createLogger, createStore
Nhận biết per-instance vs shared stateEdge Case 1 + Prediction 2
Debug shared-state bugDebug Lab — xác định đúng root cause
Thiết kế API giữ private state và invariantDesign Exercise — createWallet

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

  • [ ] Có thể giải thích tại sao hai lần gọi cùng một factory tạo được hai environment khác nhau.
  • [ ] Có thể vẽ diagram cho hai factory instances và chỉ đúng bindings của từng instance.
  • [ ] Dự đoán đúng 3/3 prediction scenarios.
  • [ ] Implement được createUser(), createLogger() và createStore() bằng closure.
  • [ ] Chứng minh được hai instance không share state bằng một test cụ thể.
  • [ ] Debug được shared-state bug bằng environment model.
  • [ ] Phân biệt được "object mới" với "local state mới".
  • [ ] Biết state declaration phải nằm ở đâu khi requirement là per-instance.
  • [ ] Thiết kế được factory nhỏ giữ private state và bảo vệ invariant.

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

Previous (Trước): Closure Formation → Closure Lifetime → Private State. Bạn đã biết function có thể giữ outer environment sau khi outer return và dùng cơ chế đó để che giấu state.

Current (Hiện tại): Factory Functions. Bạn mở rộng từ một private state sang nhiều factory invocation, mỗi invocation tạo một environment riêng và một nhóm methods cùng truy cập state của instance đó.

Next (Tiếp theo):

  • Lesson 1.4.5 — Closure trong Loop (một binding hay binding riêng cho từng iteration?)
  • Lesson 1.4.6 — Closure + Callback (function được truyền đi nhưng vẫn giữ lexical environment)
  • Stage 3 — Async JavaScript (closure sống qua thời gian và scheduling)
  • Stage 8 — React Hooks (stale closure)
  • Stage 11 — Memory & Debugging (retained references)
📴 Offline Mode — Content served from cache