Skip to content

Lesson 0.2.2 — Variables & Bindings ​

Bài 0.2.2 — Variables & Bindings
var, let, const & Sự khác biệt thực sự • 20 phút
0:00 / 0:00

0. Metadata ​

FieldValue
Stage0 — JavaScript Language Foundation
Module0.2 — Values, Variables & Types
Lesson0.2.2
CompetencyC01.2 — Variables & Bindings
DepthL2–L3 (Explain → Use)
PrerequisitesLesson 0.2.1 — Values (Primitive vs Object, Identity)
Cognitive LoadMedium

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

Bạn viết:

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

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

  1. Phân biệt declaration, initialization, assignment, reassignment.
  2. Giải thích binding giữa variable name và value.
  3. Dự đoán khi nào const gây lỗi và khi nào không.
  4. Chọn đúng giữa var, let, const trong ít nhất 4 scenario khác nhau.
  5. Nhận diện bug "tưởng const bả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ị.

Variable là Binding — score được bind với giá trị 10

let cho phép cắt nhãn dán ra và dán lại vào giá trị khác:

Let cho phép binding, score chuyển sang 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 đó.

const giữ nguyên binding, nhưng object có thể thay đổi bên trong

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
DeclarationTạo ra một variable name trong scope.
InitializationGán giá trị đầu tiên ngay tại thời điểm declaration.
AssignmentGán một giá trị cho variable.
ReassignmentGán một giá trị mới, thay thế giá trị cũ.
BindingMối liên kết giữa variable name và value tại một thời điểm.
constBinding cố định. Không cho phép reassignment.
letBinding có thể thay đổi. Cho phép reassignment.
varBinding có thể thay đổi, có behavior đặc biệt về scope (sẽ học ở Stage 1).

Supporting (Hỗ trợ) ​

  • var có thể redeclare trong cùng scope; let và const không thể.
  • const bắt buộc phải được initialize ngay khi declare.

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

  • Temporal Dead Zone (TDZ): let và const khô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: var có 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.freeze hoặ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:

js
let count = 0;
count = count + 1;

const config = { mode: "dark" };
config.mode = "light";

console.log(count);
console.log(config.mode);

Step 1 — Declaration + Initialization

js
let count = 0;

Engine tạo binding count và gán nó vào primitive value 0.

Step 2 — Reassignment

js
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

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

js
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

js
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

js
const id = 42;
id = 43;
console.log(id);

P2

js
const items = ["a"];
items.push("b");
console.log(items.length);

P3

js
let a = 1;
let a = 2;
console.log(a);

P4

js
const person = { age: 20 };
person = { age: 30 };
console.log(person.age);

P5

js
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: id là primitive. Dòng id = 43 cố gắng rebind const → lỗi.

P2

  • Output: 2
  • Why: items vẫn trỏ đến cùng một array. push là mutation bên trong object, không phải reassignment. Binding không đổi.

P3

  • Output: SyntaxError: Identifier 'a' has already been declared
  • Why: let không cho phép redeclare trong cùng scope. var thì 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 rebind person. const cấm rebinding. Mutation property thì được, nhưng reassign reference thì không.

P5

  • Output: 2 rồi SyntaxError: Identifier 'y' has already been declared
  • Why: var cho phép redeclare trong cùng scope; let cấm. Đây là lý do var dễ 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:

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

  • reportId không đổi.
  • entries có thể thêm phần tử.
  • Không dùng var.
js
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
js
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ăng value lên 1.
  • reset(): đặt value về 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
js
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); // 0

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

const bắt buộc phải khởi tạo ngay

js
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

js
const list = [1, 2];
list.push(3);      // ✅ OK — mutation
list = [1, 2, 3];  // ❌ TypeError — reassignment

Nhiề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

js
const user = { profile: { name: "A" } };
user.profile.name = "B"; // ✅ OK — nested mutation
user.profile = {};       // ❌ TypeError — reassignment of property binding

const 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

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

js
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.retries là 10 dù không có dòng code nào trực tiếp sửa defaultConfig.
  • 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):

js
console.log(cfg1 === defaultConfig); // true
console.log(cfg2 === defaultConfig); // true

Cả 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):

js
function getDefaultConfig() {
  return { retries: 3, timeout: 5000 };
}

Hoặc nếu cần dùng lại cấu trúc:

js
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_CONFIG viế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.

js
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
Variableconst / letLý do
APP_NAMEconstKhông bao giờ rebind.
currentUserletCần rebind từ null sang kết quả fetchUser().
settingsconstKhông rebind settings sang object khác; chỉ mutate property.
retryCountletCầ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:

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

  1. Tại sao const không bảo vệ API_DEFAULTS?
  2. Bạn sửa như thế nào để vẫn dùng const nhưng tránh side effect?
Đáp án tham khảo
  1. const chỉ cố định binding API_DEFAULTS. Dòng client.timeout = ... mutate object bên trong. Vì client và API_DEFAULTS trỏ cùng một object, mutation lan sang reference gốc.

  2. Fix:

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

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

  1. Tự trả lời trước: Viết ra giấy 3 câu khác biệt chính giữa let và const theo mental model "binding" mà bạn vừa học.
  2. Hỏi AI: "Explain the difference between let and const in JavaScript."
  3. 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"?
  4. 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ĩ

    • let cho phép rebind variable sang value khác.
    • const cố định binding, không cho rebind.
    • const vẫn cho phép mutation nếu value là object.
    • const bắt buộc initialize ngay.
  • AI trả lời

    • AI thường nói: "const is used for variables whose values do not change. let is 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ồ.
  • So sánh

    • AI đúng ở bề mặt: const khô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.
  • Đ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ể push vào một const array 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ĩ const như 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. const cấm điều đó."
    • "Tóm lại: const bảo vệ nhãn dán (binding), không bảo vệ món đồ (value bên trong object)."

💡 Liên tưởng: const giố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 const với Date, 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 / reassignmentExplanation (Giải thích)
Giải thích binding bằng mental modelTeach-back (Dạy lại)
Dự đoán const error vs mutation cho phépPrediction (Dự đoán)
Chọn var / let / const đúngDesign Decision (Quyết định thiết kế)
Nhận diện mutation bug với const objectDebug 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 const hoặc let cho một variable mới và bảo vệ quyết định đó.
  • [ ] Có thể debug một trường hợp const object bị mutate ngoài ý muốn.
  • [ ] Không còn dùng var trong 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. const cố đị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 const và readonly types sẽ mở rộng khái niệm immutability từ runtime sang static type.
📴 Offline Mode — Content served from cache