Lesson 0.2.2 — Variables & Bindings
0. Metadata
| Field | Value |
|---|---|
| Stage | 0 — JavaScript Language Foundation |
| Module | 0.2 — Values, Variables & Types |
| Lesson | 0.2.2 |
| Competency | C01.2 — Variables & Bindings |
| Depth | L2–L3 (Explain → Use) |
| Prerequisites | Lesson 0.2.1 — Values (Primitive vs Object, Identity) |
| Cognitive Load | Medium |
1. Why This Exists (Vì sao cần học)
Bạn viết:
const user = { name: "Alice" };
user.name = "Bob";
console.log(user.name); // "Bob"Code chạy. Không lỗi. Nhưng bạn khai báo const — tức là constant, đúng không? Tại sao giá trị vẫn đổi?
Hoặc bạn viết:
const count = 0;
count = 1;Và nhận ngay TypeError: Assignment to constant variable.
Cùng là const, một cái cho phép thay đổi, một cái không. Nếu không phân biệt được binding (mối liên kết) và value (giá trị), bạn sẽ luôn chọn let cho mọi thứ "cho an toàn" — và mất đi một công cụ quan trọng để diễn đạt ý định trong code.
Lesson này xây dựng mental model về variable là một binding chứ không phải một cái hộp đựng giá trị.
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 được primitive value và object value (Lesson 0.2.1).
- Hiểu rằng object có identity riêng, có thể bị thay đổi nội dung mà không đổi identity.
Nếu chưa rõ, quay lại Lesson 0.2.1 trước.
3. Learning Objectives (Mục tiêu học tập)
Sau bài này, bạn có thể:
- Phân biệt declaration, initialization, assignment, reassignment.
- Giải thích binding giữa variable name và value.
- Dự đoán khi nào
constgây lỗi và khi nào không. - Chọn đúng giữa
var,let,consttrong ít nhất 4 scenario khác nhau. - Nhận diện bug "tưởng
constbảo vệ object khỏi mutation".
4. Mental Model (Mô hình tư duy)
Mental Model — Variable là Binding, không phải Hộp
Đừng hình dung variable là một chiếc hộp chứa giá trị.
Hãy hình dung variable là một nhãn dán (label) được buộc (bind) vào một giá trị.
let cho phép cắt nhãn dán ra và dán lại vào giá trị khác:
const nghĩa là nhãn dán được dán chặt — bạn không thể cắt nó ra dán sang giá trị khác. Nhưng nếu giá trị đó là một object, bạn vẫn có thể thay đổi bên trong object đó.
Binding không đổi. Value bên trong đổi.
5. Core Concepts (Các khái niệm cốt lõi)
Essential (Bắt buộc)
| Khái niệm | Ý nghĩa |
|---|---|
| Declaration | Tạo ra một variable name trong scope. |
| Initialization | Gán giá trị đầu tiên ngay tại thời điểm declaration. |
| Assignment | Gán một giá trị cho variable. |
| Reassignment | Gán một giá trị mới, thay thế giá trị cũ. |
| Binding | Mối liên kết giữa variable name và value tại một thời điểm. |
const | Binding cố định. Không cho phép reassignment. |
let | Binding có thể thay đổi. Cho phép reassignment. |
var | Binding có thể thay đổi, có behavior đặc biệt về scope (sẽ học ở Stage 1). |
Supporting (Hỗ trợ)
varcó thể redeclare trong cùng scope;letvàconstkhông thể.constbắt buộc phải được initialize ngay khi declare.
Awareness (Biết tồn tại)
- Temporal Dead Zone (TDZ):
letvàconstkhông thể truy cập trước khi đến dòng khai báo. Sẽ được giải thích chi tiết ở Stage 1. - Hoisting:
varcó behavior đặc biệt về thứ tự khả dụng. Sẽ được giải thích chi tiết ở Stage 1.
Out of Scope (Không học trong bài này)
- Execution Context (Stage 1).
- Scope chain và Lexical Environment (Stage 1).
- Closure (Stage 1).
Object.freezehoặc immutable patterns (Stage 2, Stage 6).
6. Worked Example (Ví dụ phân tích từng bước)
Xét đoạn code sau:
let count = 0;
count = count + 1;
const config = { mode: "dark" };
config.mode = "light";
console.log(count);
console.log(config.mode);Step 1 — Declaration + Initialization
let count = 0;Engine tạo binding count và gán nó vào primitive value 0.
Step 2 — Reassignment
count = count + 1;Engine tính count + 1 → 1. Sau đó cắt binding cũ và dán lại count vào 1. Primitive value 0 không thể bị thay đổi. Binding count giờ trỏ sang giá trị mới 1.
Step 3 — Declaration + Initialization với Object
const config = { mode: "dark" };Engine tạo binding config. Vì là const, binding này sẽ cố định. Nó trỏ đến một object value { mode: "dark" }.
Step 4 — Mutation (không phải Reassignment)
config.mode = "light";Engine không thay đổi binding config. Nó vẫn trỏ đến cùng một object. Engine thay đổi property bên trong object đó. Đây là mutation, không phải reassignment.
Step 5 — Output
console.log(count); // 1
console.log(config.mode); // "light"Code Review Lens
Khi review code, hãy tự hỏi: "Dòng này đang rebind variable hay đang mutate object?" Câu trả lời quyết định const có phù hợp hay không.
7. Prediction Exercise (Bài tập dự đoán)
Đừng chạy code. Dự đoán output hoặc lỗi, và giải thích bằng mental model "binding vs value".
P1
const id = 42;
id = 43;
console.log(id);P2
const items = ["a"];
items.push("b");
console.log(items.length);P3
let a = 1;
let a = 2;
console.log(a);P4
const person = { age: 20 };
person = { age: 30 };
console.log(person.age);P5
var x = 1;
var x = 2;
let y = 1;
let y = 2;
console.log(x, y);Đáp án & Giải thích
P1
- Output:
TypeError: Assignment to constant variable. - Why:
idlà primitive. Dòngid = 43cố gắng rebindconst→ lỗi.
P2
- Output:
2 - Why:
itemsvẫn trỏ đến cùng một array.pushlà mutation bên trong object, không phải reassignment. Binding không đổi.
P3
- Output:
SyntaxError: Identifier 'a' has already been declared - Why:
letkhông cho phép redeclare trong cùng scope.varthì cho phép.
P4
- Output:
TypeError: Assignment to constant variable. - Why: Dòng
person = { age: 30 }tạo một object mới và cố gắng rebindperson.constcấm rebinding. Mutation property thì được, nhưng reassign reference thì không.
P5
- Output:
2rồiSyntaxError: Identifier 'y' has already been declared - Why:
varcho phép redeclare trong cùng scope;letcấm. Đây là lý dovardễ gây bug trong code lớn.
8. Implementation Lab (Bài lab thực hành)
Lab A — Guided (Có hướng dẫn)
Chuyển đoạn code sau sang dùng const ở mọi nơi có thể, và giữ let ở nơi bắt buộc phải rebind:
var name = "App";
var version = 1;
var settings = { theme: "dark" };
version = 2;
settings.theme = "light";
console.log(name, version, settings.theme);Đáp án tham khảo
const name = "App"; // không rebind
let version = 1; // cần rebind ở dòng sau
const settings = { theme: "dark" }; // object, chỉ mutate
version = 2;
settings.theme = "light";
console.log(name, version, settings.theme);Lab B — Partial Scaffold (Khung sườn)
Hoàn thành hàm buildReport sao cho:
reportIdkhông đổi.entriescó thể thêm phần tử.- Không dùng
var.
function buildReport(initialEntries) {
// TODO: khai báo reportId bằng const
// TODO: khai báo entries sao cho có thể push thêm
// implement here
return { id: reportId, data: entries };
}Đáp án tham khảo
function buildReport(initialEntries) {
const reportId = Math.random().toString(36).slice(2);
const entries = [...initialEntries]; // const vì ta không rebind entries
entries.push({ timestamp: Date.now() });
return { id: reportId, data: entries };
}Lab C — Independent (Tự làm)
Viết một hàm createCounter trả về một object có:
value: số đếm hiện tại (bắt đầu từ 0).increment(): tăngvaluelên 1.reset(): đặtvaluevề 0.
Yêu cầu: dùng const cho object trả về, nhưng vẫn cho phép increment và reset hoạt động đúng.
Đáp án tham khảo
function createCounter() {
const counter = {
value: 0,
increment() {
this.value += 1;
},
reset() {
this.value = 0;
}
};
return counter;
}
const c = createCounter();
c.increment();
console.log(c.value); // 1
c.reset();
console.log(c.value); // 09. Edge Cases (Các trường hợp ngoại lệ)
const bắt buộc phải khởi tạo ngay
const name;
name = "Alice";Lỗi: SyntaxError: Missing initializer in const declaration
const yêu cầu binding phải gắn với một value ngay tại declaration. Không có "declare trước, assign sau" với const.
const với Array
const list = [1, 2];
list.push(3); // ✅ OK — mutation
list = [1, 2, 3]; // ❌ TypeError — reassignmentNhiều developer nhầm rằng const [] có nghĩa array không đổi. Sai. Binding không đổi, nhưng nội dung array hoàn toàn có thể thay đổi.
const với Nested Object
const user = { profile: { name: "A" } };
user.profile.name = "B"; // ✅ OK — nested mutation
user.profile = {}; // ❌ TypeError — reassignment of property bindingconst chỉ cố định binding của user với object ngoài cùng. Nếu object đó chứa nested object, các nested object vẫn có thể bị mutate hoàn toàn. Đây là lý do const không thay thế được deep clone hay immutable data structures.
var có thể redeclare
var x = 1;
var x = 2; // ✅ Không lỗiĐây là một trong những lý do var dễ gây bug trong code lớn. let và const bảo vệ bạn khỏi việc vô tình tạo lại variable.
10. Debug Lab (Bài lab gỡ lỗi)
Symptom (Triệu chứng): Hàm getDefaultConfig trả về config, nhưng sau vài lần gọi, config ban đầu bị thay đổi.
Reproduction (Tái hiện lỗi):
const defaultConfig = { retries: 3, timeout: 5000 };
function getDefaultConfig() {
return defaultConfig;
}
const cfg1 = getDefaultConfig();
cfg1.retries = 10;
const cfg2 = getDefaultConfig();
console.log(cfg2.retries); // 10 ??? Tôi đâu có đổi default!Evidence (Bằng chứng):
cfg2.retrieslà10dù không có dòng code nào trực tiếp sửadefaultConfig.- Không có lỗi
TypeError.
Hypothesis (Giả thuyết): Có thể getDefaultConfig không tạo object mới mà trả về cùng một reference. Ai đó đã mutate object đó.
Verification (Xác minh):
console.log(cfg1 === defaultConfig); // true
console.log(cfg2 === defaultConfig); // trueCả hai đều trỏ đến cùng một object. const chỉ bảo vệ binding defaultConfig, không bảo vệ object khỏi mutation.
Root Cause (Nguyên nhân gốc rễ):const ngăn reassignment, không ngăn mutation. Khi trả về object từ const, bạn đang trả về reference — caller có thể sửa trực tiếp.
Fix (Sửa):
function getDefaultConfig() {
return { retries: 3, timeout: 5000 };
}Hoặc nếu cần dùng lại cấu trúc:
function getDefaultConfig() {
return { ...defaultConfig };
}Prevention (Phòng ngừa):
- Nếu hàm trả về object dùng chung, hãy tạo bản sao (shallow copy) trước khi trả về.
- Đặt tên rõ ràng:
DEFAULT_CONFIGviết hoa để báo hiệu "không nên mutate".
11. Design Exercise (Bài tập thiết kế giải pháp)
Context
Bạn đang review một file config trong dự án. Mỗi dòng dưới đây đang dùng let. Hãy quyết định nên dùng const hay giữ let, và giải thích bằng mental model binding.
let APP_NAME = "MyApp";
let currentUser = null;
currentUser = fetchUser();
let settings = loadSettings();
settings.theme = "dark";
let retryCount = 0;
retryCount += 1;Đáp án tham khảo
| Variable | const / let | Lý do |
|---|---|---|
APP_NAME | const | Không bao giờ rebind. |
currentUser | let | Cần rebind từ null sang kết quả fetchUser(). |
settings | const | Không rebind settings sang object khác; chỉ mutate property. |
retryCount | let | Cần rebind sang giá trị số mới (+= 1). |
Quy tắc thực tế: Mặc định dùng const. Chỉ đổi sang let khi bạn thực sự cần rebind variable sang value khác.
12. Production Scenario (Tình huống thực tế)
Bạn đang làm việc trong một team frontend. Một đồng nghiệp viết:
const API_DEFAULTS = { baseURL: "/api", timeout: 5000 };
function createClient(opts) {
const client = API_DEFAULTS;
client.timeout = opts.timeout || client.timeout;
return client;
}Sau vài tuần, team nhận thấy tất cả API calls đều có timeout khác nhau — và không ai hiểu tại sao API_DEFAULTS không còn là 5000 nữa.
Câu hỏi:
- Tại sao
constkhông bảo vệAPI_DEFAULTS? - Bạn sửa như thế nào để vẫn dùng
constnhưng tránh side effect?
Đáp án tham khảo
constchỉ cố định bindingAPI_DEFAULTS. Dòngclient.timeout = ...mutate object bên trong. VìclientvàAPI_DEFAULTStrỏ cùng một object, mutation lan sang reference gốc.Fix:
function createClient(opts) {
const client = { ...API_DEFAULTS }; // shallow copy
client.timeout = opts.timeout || client.timeout;
return client;
}Hoặc tốt hơn, không dùng object mutable làm default:
function createClient(opts) {
const client = {
baseURL: API_DEFAULTS.baseURL,
timeout: opts.timeout ?? API_DEFAULTS.timeout
};
return client;
}13. AI-assisted Exercise (Bài tập với AI)
Level B — Challenge (Thách thức AI)
- Tự trả lời trước: Viết ra giấy 3 câu khác biệt chính giữa
letvàconsttheo mental model "binding" mà bạn vừa học. - Hỏi AI: "Explain the difference between let and const in JavaScript."
- So sánh: Câu trả lời AI có nhắc đến binding và mutation vs reassignment không? Hay AI chỉ nói "const is for values that don't change"?
- Verify: Kiểm tra lại bằng MDN page "const" để xem documentation chính thức dùng từ gì để mô tả behavior này.
Gợi ý
Hãy để ý xem AI có nói "const prevents changing values" hay không. Đó chính xác là điểm mù phổ biến: AI thường diễn đạt như thể const bảo vệ value, nhưng thực tế nó chỉ bảo vệ binding.
Đáp án tham khảo
Bạn nghĩ
letcho phép rebind variable sang value khác.constcố định binding, không cho rebind.constvẫn cho phép mutation nếu value là object.constbắt buộc initialize ngay.
AI trả lời
- AI thường nói: "
constis used for variables whose values do not change.letis used when you need to reassign." - AI có thể thêm: "However, objects declared with const can still be modified."
- Nhưng AI hiếm khi dùng từ binding; thay vào đó dùng "variable" hoặc "value" một cách mơ hồ.
- AI thường nói: "
So sánh
- AI đúng ở bề mặt:
constkhông reassign, object vẫn modify được. - AI thiếu: khái niệm binding chính xác. AI không giải thích tại sao primitive bị chặn còn object thì không — vì primitive không thể mutate, chỉ có thể rebind.
- AI đúng ở bề mặt:
Điểm AI nói sai hoặc quá mơ hồ
- Cụm "values do not change" gây hiểu nhầm: với object, value (object reference) thực sự không đổi, nhưng nội dung thì đổi. AI không phân biệt reference và content.
- AI thường không nhắc đến Temporal Dead Zone hoặc hoisting difference giữa
var/let/const.
Kết luận
- Nếu bạn chỉ ra được rằng AI đang nhầm lẫn giữa "value không đổi" và "binding không đổi", bạn đã hiểu sâu hơn AI. Đây là lý do tại sao documentation chính thức (MDN, ECMAScript spec) quan trọng hơn câu trả lời AI.
- Concept này sẽ quay lại ở Stage 1 (Scope + TDZ) và Stage 6 (TypeScript
as const).
14. Teach Back (Dạy lại)
Giải thích cho một đồng nghiệp junior trong 2 phút, không dùng từ "nó thế":
"Tại sao tôi có thể
pushvào mộtconstarray nhưng không thể gán array mới cho cùng một variable đó?"
Yêu cầu:
- Dùng đúng từ: binding, reassignment, mutation, reference.
- Không được nói "vì JavaScript cho phép vậy".
Mô phỏng
- Bạn nói
- "Nghĩ
constnhư một nhãn dán được dán chặt vào một món đồ." - "Array là một món đồ object. Khi bạn
push, bạn đang thêm đồ bên trong món đồ đó — nhãn dán vẫn dán vào cùng một món đồ, nên không sao." - "Nhưng khi bạn gán array mới, bạn đang cố gỡ nhãn dán ra dán sang món đồ khác.
constcấm điều đó." - "Tóm lại:
constbảo vệ nhãn dán (binding), không bảo vệ món đồ (value bên trong object)."
- "Nghĩ
💡 Liên tưởng:
constgiống như bạn thuê một căn hộ cố định địa chỉ. Bạn không thể đổi sang căn hộ khác, nhưng bạn vẫn có thể kê bàn, di chuyển ghế bên trong.
Gợi ý đánh giá bản thân
- Đồng nghiệp của bạn có hỏi lại "vậy object.freeze là gì?" không? Nếu có, bạn đã truyền đạt đủ rõ để họ muốn đào sâu.
- Bạn có thể dự đoán được behavior của
constvớiDate,RegExp, hay plain object không?
15. Assessment (Đánh giá)
| Objective (Mục tiêu) | Hình thức đánh giá |
|---|---|
| Phân biệt declaration / initialization / assignment / reassignment | Explanation (Giải thích) |
| Giải thích binding bằng mental model | Teach-back (Dạy lại) |
Dự đoán const error vs mutation cho phép | Prediction (Dự đoán) |
Chọn var / let / const đúng | Design Decision (Quyết định thiết kế) |
Nhận diện mutation bug với const object | Debug Lab (Gỡ lỗi) |
16. Exit Criteria (Tiêu chí qua bài)
- [ ] Có thể giải thích sự khác biệt giữa binding và value bằng lời của riêng mình.
- [ ] Có thể dự đoán đúng 4/4 scenarios trong Prediction Exercise.
- [ ] Có thể chọn
consthoặcletcho một variable mới và bảo vệ quyết định đó. - [ ] Có thể debug một trường hợp
constobject bị mutate ngoài ý muốn. - [ ] Không còn dùng
vartrong code mới trừ khi có lý do rõ ràng (awareness).
17. Spiral Connection (Liên kết xoắn ốc)
Previous (Trước): Lesson 0.2.1 — Values. Bạn đã biết phân biệt primitive và object, hiểu identity và equality.
Current (Hiện tại): Lesson 0.2.2 — Variables & Bindings. Bạn hiểu variable là binding, không phải hộp đựng.
constcố định binding, không cố định object content.
Next (Tiếp theo):
- Lesson 0.2.3 — Primitive Types (đào sâu vào từng loại primitive).
- Lesson 0.2.4 — Object Values (đào sâu vào object behavior).
- Stage 1 — Execution Model: Scope, Hoisting, TDZ, Closure. Tất cả đều xây dựng trên nền tảng binding bạn vừa học.
- Stage 6 — TypeScript:
as constvà readonly types sẽ mở rộng khái niệm immutability từ runtime sang static type.