- Hiểu 3 cách khai báo biến:
var,let,constvà khi nào dùng cái nào. - Biết 7 primitive types và
typeoftrả về gì cho từng cái. - Phân biệt
nullvsundefined— khi nào JS sinh cái nào. - Master type coercion: implicit (
+,==,if) vs explicit (Number(),String(),Boolean()). - Tránh gotcha kinh điển:
NaN !== NaN,0.1 + 0.2,typeof null === 'object'. - Hiểu truthy/falsy và 6 falsy values.
1. Khai báo biến: var, let, const
JavaScript có 3 keyword khai báo biến. Hiểu khác nhau là quan trọng:
| — | var | let | const |
|---|---|---|---|
| Phiên bản | Từ ES1 (1997) | ES6 (2015) | ES6 (2015) |
| Scope | Function scope | Block scope | Block scope |
| Hoisting | Có, init = undefined | Có nhưng ở TDZ | Có nhưng ở TDZ |
| Reassign | OK | OK | ❌ (lỗi) |
| Redeclare cùng scope | OK (silent) | ❌ (lỗi) | ❌ (lỗi) |
| Khuyến nghị | Tránh | Khi cần reassign | Mặc định |
Demo block scope:
if (true) {
var a = 1;
let b = 2;
const c = 3;
}
console.log(a); // 1 ← var "leak" ra ngoài block
console.log(b); // ReferenceError: b is not defined
console.log(c); // ReferenceError: c is not defined
Demo hoisting + TDZ (Temporal Dead Zone):
// var — hoisted với value = undefined
console.log(x); // undefined (không lỗi)
var x = 5;
// let — hoisted nhưng ở TDZ → lỗi khi truy cập
console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 5;
Tưởng tượng JS đọc code 2 lượt. Lượt 1: ghi nhận mọi khai báo (var/let/const/function) — "có 1 biến tên x ở đây". Lượt 2: chạy code thật. Giữa "lượt 1 đã thấy" và "lượt 2 đến dòng khai báo" là TDZ.
var trong TDZ có value undefined (không lỗi). let/const trong
TDZ throw ReferenceError. Mục đích: ép developer khai báo trước khi dùng.
Quy tắc cho code mới (2026):
- Mặc định
const. Hầu hết biến không cần reassign. - Đổi sang
letchỉ khi thật sự cần reassign (counter loop, accumulator). - Không dùng
var. Đọc code cũ thì biết là gì, nhưng đừng viết mới.
const không freeze object
const chỉ chặn reassign biến, không chặn mutate object/array.
const obj = { a: 1 };
obj.a = 2; // OK! mutate property
obj.b = 3; // OK! thêm property
obj = {}; // ❌ TypeError: Assignment to constant variable
Muốn freeze object thật sự: Object.freeze(obj). Nhưng nó chỉ shallow — nested object vẫn mutate được.
2. 7 Primitive types
JavaScript có 7 primitive types + 1 không-phải-primitive (Object). Primitive là immutable — bạn không thể "sửa" 1 primitive, chỉ tạo cái mới.
| Type | Ví dụ | typeof | Ghi chú |
|---|---|---|---|
string | 'abc', "hi", `hello` | 'string' | UTF-16 internal |
number | 42, 3.14, NaN, Infinity | 'number' | IEEE 754 double |
bigint | 9007199254740993n | 'bigint' | ES2020. Cho số > MAX_SAFE_INTEGER |
boolean | true, false | 'boolean' | Chỉ 2 giá trị |
undefined | undefined | 'undefined' | JS tự sinh khi biến chưa init |
null | null | 'object' 🔥 | Developer explicit gán |
symbol | Symbol('id') | 'symbol' | ES6. Unique identifier |
| Object | {}, [], function | 'object' / 'function' | Mọi thứ còn lại |
typeof null
typeof null === 'object' là một bug từ JavaScript 1995. Brendan Eich biết về nó nhưng không thể fix
vì sẽ break web — quá nhiều code đã rely on bug này.
Để check null: x === null hoặc x == null (cũng match undefined).
2.1. number — chỉ có 1 type số
Khác Dart (int, double) hoặc Python (int arbitrary precision, float),
JavaScript chỉ có 1 type duy nhất cho cả số nguyên và thập phân — number. Internal
là IEEE 754 double-precision floating point (64-bit).
typeof 42; // 'number'
typeof 3.14; // 'number' — không có 'float' riêng
typeof NaN; // 'number' 🔥 (NaN là một loại number)
typeof Infinity; // 'number'
Number.MAX_SAFE_INTEGER; // 9007199254740991 (2^53 - 1)
Number.MIN_SAFE_INTEGER; // -9007199254740991
Number.EPSILON; // 2.220446049250313e-16
// Vượt MAX_SAFE_INTEGER → mất độ chính xác
9007199254740993 === 9007199254740992; // true (!!)
Cho số lớn hơn MAX_SAFE_INTEGER (vd: id của Twitter, financial calculation), dùng BigInt:
const big = 9007199254740993n; // hậu tố `n`
const big2 = BigInt("9007199254740993");
// Không thể trộn BigInt với Number
big + 1; // ❌ TypeError
big + 1n; // ✅ 9007199254740994n
0.1 + 0.2 === 0.3 trả về false. Vì IEEE 754 không biểu diễn được chính xác
0.1 hay 0.2 trong binary.
0.1 + 0.2; // 0.30000000000000004
0.1 + 0.2 === 0.3; // false
// Cách so sánh đúng:
Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON; // true
// Hoặc dùng toFixed():
(0.1 + 0.2).toFixed(2); // '0.30' (string)
Cho tính tiền chính xác, dùng thư viện decimal.js, big.js hoặc Decimal của
Money library (vd dinero.js).
2.2. string và template literal
3 cách tạo string. Backtick có khả năng đặc biệt:
const a = 'single quote';
const b = "double quote";
const c = `template literal`;
// Template literal hỗ trợ:
// 1. Interpolation
const name = 'Việt';
const greet = `Xin chào, ${name}!`; // 'Xin chào, Việt!'
// 2. Multiline
const text = `Dòng 1
Dòng 2
Dòng 3`;
// 3. Expression bên trong
const sum = `${2 + 3}`; // '5'
// 4. Function call
const upper = `${name.toUpperCase()}`; // 'VIỆT'
String immutable — không sửa được ký tự, chỉ tạo string mới:
const s = 'abc';
s[0] = 'z'; // silent fail (strict mode: lỗi)
console.log(s); // 'abc' — không đổi
// Phải tạo mới:
const s2 = 'z' + s.slice(1); // 'zbc'
2.3. null vs undefined — khi nào cái nào
Hai giá trị "rỗng" của JS. Quy ước:
| — | undefined | null |
|---|---|---|
| Ai sinh | JS tự sinh | Developer explicit gán |
| Khi nào xuất hiện | Biến khai báo chưa init, missing arg, missing property, return ngầm | Khi muốn "explicit empty" |
typeof | 'undefined' | 'object' 🔥 |
let x;
console.log(x); // undefined (chưa init)
function foo(a, b) { return [a, b]; }
foo(1); // [1, undefined] — missing arg
const obj = { a: 1 };
console.log(obj.b); // undefined — missing property
function noReturn() {}
noReturn(); // undefined — implicit return
// document.getElementById trả null nếu không tìm thấy
const el = document.getElementById('foo');
if (el === null) { /* xử lý */ }
// Explicit set: "tôi cố ý không có value"
let selectedUser = null;
// ... sau này:
selectedUser = { id: 1, name: 'An' };
Check cả 2 cùng lúc với == null (chấp nhận coercion cho 1 case duy nhất):
// x == null match cả null và undefined
if (x == null) { /* x là null hoặc undefined */ }
// Tương đương:
if (x === null || x === undefined) { }
3. Truthy / Falsy
Khi JS cần boolean (vd: if, !, &&, ||), nó coerce value qua Boolean(value). Có 6 giá trị falsy:
false0(và-0,0nBigInt)''(empty string)nullundefinedNaN
Mọi giá trị khác đều truthy, kể cả những trường hợp dễ nhầm:
Boolean([]); // true 🔥 (array rỗng vẫn truthy)
Boolean({}); // true 🔥 (object rỗng vẫn truthy)
Boolean('0'); // true (string '0' truthy, khác number 0)
Boolean('false'); // true (string 'false' truthy)
Boolean(Infinity); // true
Boolean(function(){}); // true (function là object)
// Bug: muốn check array có phần tử
const arr = [];
if (arr) {
console.log('có phần tử'); // chạy! vì [] truthy
}
// Đúng: check length
if (arr.length > 0) { }
4. Type Coercion — implicit conversion
Là nguồn chính của bug "weird JS". Có 3 ngữ cảnh implicit coerce:
- String context —
+với string, template literal,String(). - Number context — toán tử số (
-,*,/,%), so sánh (<,>). - Boolean context —
if,!,&&,||, ternary.
4.1. Toán tử + — string hay number?
Quy tắc: nếu bất kỳ operand là string, kết quả là string (concat). Còn lại thì number.
1 + 2; // 3
1 + '2'; // '12' 🔥 (number → string, concat)
'1' + 2; // '12'
'1' + '2'; // '12'
1 + true; // 2 (true → 1)
1 + false; // 1 (false → 0)
1 + null; // 1 (null → 0)
1 + undefined; // NaN (undefined → NaN)
// Trick: convert sang number bằng `+`
+'42'; // 42 (number)
+'42px'; // NaN
4.2. Toán tử khác — luôn number
'5' - 3; // 2 ('5' → 5)
'5' * '2'; // 10 (cả 2 → number)
'10' / 2; // 5
'abc' - 1; // NaN (abc không là number hợp lệ)
4.3. == vs === — Đẳng thức
=== (strict): so sánh value + type. Không coerce.
== (loose): coerce trước khi so sánh. Rules phức tạp.
1 === 1; // true
1 === '1'; // false (type khác)
1 == '1'; // true ('1' → 1, sau đó 1 == 1)
0 == false; // true (false → 0)
0 == ''; // true ('' → 0)
0 == '0'; // true
null == undefined; // true (chỉ 2 cái này == nhau)
null === undefined; // false
null == 0; // false 🔥 (null không coerce sang 0 trong ==)
Bảng coercion == đầy đủ:
| — | undefined | null | number | string | boolean |
|---|---|---|---|---|---|
| undefined | true | true | false | false | false |
| null | true | true | false | false | false |
| number | false | false | so sánh value | convert string → number | convert boolean → number |
| string | false | false | convert string → number | so sánh value | cả 2 → number |
| boolean | false | false | convert boolean → number | cả 2 → number | so sánh value |
Luôn dùng ===. Chỉ ngoại lệ: x == null để check đồng thời null và
undefined (vẫn có người không thích, viết x === null || x === undefined cho rõ ràng).
ESLint rule eqeqeq sẽ catch == tự động.
5. Explicit coercion — convert tường minh
3 function global:
// String() — convert sang string
String(42); // '42'
String(true); // 'true'
String(null); // 'null'
String(undefined); // 'undefined'
String([1,2,3]); // '1,2,3'
String({a:1}); // '[object Object]' 🔥
// Number() — convert sang number
Number('42'); // 42
Number('42.5'); // 42.5
Number('42px'); // NaN (không parse partial)
Number(''); // 0 🔥
Number(' '); // 0 🔥 (whitespace → 0)
Number(null); // 0
Number(undefined); // NaN
Number([]); // 0 🔥
Number([42]); // 42 (array 1 phần tử)
Number([1,2]); // NaN
// Boolean() — convert sang boolean (theo rule truthy/falsy)
Boolean(0); // false
Boolean(''); // false
Boolean('0'); // true (string '0' truthy)
// parseInt / parseFloat — parse partial
parseInt('42px'); // 42 (parse số đầu, ignore phần còn lại)
parseFloat('3.14em'); // 3.14
parseInt('abc'); // NaN
+x; // = Number(x)
'' + x; // = String(x)
!!x; // = Boolean(x)
Ngắn gọn, nhưng hơi cryptic. Trong code production khuyên dùng full function name.
6. NaN — "Not a Number" nhưng là number
NaN là kết quả của phép toán không hợp lệ. Tính chất đặc biệt: không bằng chính nó.
NaN === NaN; // false 🔥
NaN == NaN; // false
typeof NaN; // 'number' 🔥
// Khi NaN xuất hiện
0 / 0; // NaN
Math.sqrt(-1); // NaN
Number('abc'); // NaN
undefined + 1; // NaN
// Check NaN — dùng Number.isNaN (chính xác hơn isNaN cũ)
Number.isNaN(NaN); // true
Number.isNaN('abc'); // false 🔥 ('abc' không phải NaN, nó là string)
isNaN('abc'); // true ❌ (global isNaN coerce trước → bug)
isNaN vs Number.isNaN
Global isNaN(x) chạy Number(x) trước rồi check NaN — gây false positive.
Number.isNaN(x) (ES6) check chính xác: chỉ true khi x thực sự là NaN.
Luôn dùng Number.isNaN.
7. Optional chaining ?. và Nullish coalescing ??
2 toán tử ES2020 cực kỳ hữu ích, tránh được rất nhiều bug.
7.1. ?. — Optional chaining
Truy cập property/method an toàn. Nếu giữa chừng có null/undefined, kết quả là undefined thay vì throw.
const user = { name: 'An', address: { city: 'Hà Nội' } };
const user2 = { name: 'Bình' };
// Cũ (verbose):
const city = user2 && user2.address && user2.address.city;
// Mới:
const city2 = user2?.address?.city; // undefined (không throw)
// Áp dụng cho method call
user.greet?.(); // nếu greet không tồn tại, không lỗi
// Áp dụng cho array index
const arr = null;
const first = arr?.[0]; // undefined
7.2. ?? — Nullish coalescing
Trả vế phải chỉ khi vế trái là null hoặc undefined. Khác || ở chỗ || còn match mọi falsy (0, '', false).
// Default value pattern
const count = userInput ?? 0;
const name = userName ?? 'Anonymous';
// Khác || ở case 0, '', false:
0 || 5; // 5 (0 falsy)
0 ?? 5; // 0 (0 không phải null/undefined)
'' || 'x'; // 'x'
'' ?? 'x'; // ''
false || true; // true
false ?? true; // false
Khi 0, '', false là giá trị hợp lệ (vd: count = 0, prefix rỗng) — dùng ??.
Khi muốn fallback cho tất cả falsy — dùng ||. Trong app modern, ?? phù hợp hơn 90% case.
Bài tập
Bài 1 — Đoán typeof
Không chạy, đoán kết quả của:
console.log(typeof null);
console.log(typeof undefined);
console.log(typeof NaN);
console.log(typeof []);
console.log(typeof function(){});
console.log(typeof Symbol());
console.log(typeof 42n);
Sau đó chạy thử và đối chiếu.
Đáp án
typeof null === 'object'🔥 (bug lịch sử)typeof undefined === 'undefined'typeof NaN === 'number'🔥 (NaN là number)typeof [] === 'object'(array là object)typeof function(){} === 'function'(special)typeof Symbol() === 'symbol'typeof 42n === 'bigint'
Bài 2 — isEmpty function
Viết function isEmpty(value) trả true nếu value là:
nullhoặcundefined- String rỗng
'' - Array rỗng
[] - Object rỗng
{}
Test với ít nhất 10 case, bao gồm cả truthy (0, false) — không nên match.
Đáp án mẫu
function isEmpty(value) {
if (value == null) return true; // null hoặc undefined
if (typeof value === 'string') return value === '';
if (Array.isArray(value)) return value.length === 0;
if (typeof value === 'object') {
return Object.keys(value).length === 0;
}
return false; // number, boolean, function: không "empty"
}
isEmpty(null); // true
isEmpty(undefined); // true
isEmpty(''); // true
isEmpty([]); // true
isEmpty({}); // true
isEmpty(0); // false
isEmpty(false); // false
isEmpty('0'); // false
isEmpty([0]); // false
isEmpty({a:1}); // false
Bài 3 — Strict integer check
Viết function isStrictInteger(x) trả true chỉ khi x là số nguyên thật sự (không phải string số, không phải float).
Test:
isStrictInteger(5)→ trueisStrictInteger(5.0)→ true (vì 5.0 === 5)isStrictInteger(5.5)→ falseisStrictInteger('5')→ falseisStrictInteger(NaN)→ false
Đáp án
function isStrictInteger(x) {
return Number.isInteger(x);
}
// Number.isInteger là method ES6, kiểm tra:
// - type number
// - không phải NaN/Infinity
// - là integer (không phần thập phân)
Bài 4 — Loop và closure
Code dưới in ra 3 3 3 sau 1 giây. Sửa thành 0 1 2 bằng 2 cách độc lập.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
2 cách fix
Cách 1: đổi var → let. let tạo binding mới mỗi iteration → closure capture đúng giá trị.
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
Cách 2: IIFE để tạo scope riêng cho từng iteration.
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 1000);
})(i);
}
Chi tiết closure ở chương 3.
Bài 5 — safeJsonParse
Viết safeJsonParse(str) parse JSON, nếu lỗi trả null.
Thử với:
safeJsonParse('{"a": 1}')→{ a: 1 }safeJsonParse('invalid')→nullsafeJsonParse('null')→ ?safeJsonParse('undefined')→ ?
Đáp án + giải thích
function safeJsonParse(str) {
try {
return JSON.parse(str);
} catch {
return null;
}
}
'null'→null(JSON parse hợp lệ, kết quả null).'undefined'→ null (vìundefinedkhông phải JSON hợp lệ → throw → catch → return null).
Lưu ý: trả null cho cả "parse fail" và "parse được null" — caller không phân biệt. Pattern Result type (chương 7) giải quyết.
Quiz
typeof null trả về gì?
Xem đáp án
'object'. Đây là bug từ JavaScript 1995 không thể fix vì backward compat. Cách check null đúng: x === null.
[] == false trả về gì?
Xem đáp án
true. Coercion: [] → '' → 0; false → 0; cuối cùng 0 == 0. Đây là lý do nên dùng ===.
const obj = { a: 1 }; obj.a = 2; có lỗi không?
Xem đáp án
Không. const chỉ chặn reassign biến (obj = ...), không chặn mutate property bên trong. Muốn freeze: Object.freeze(obj).
if (x) với x = [] — chạy nhánh nào?
Xem đáp án
Nhánh true. [] là object, mọi object đều truthy. Để check array có phần tử: arr.length > 0.
NaN === NaN trả về gì? Vì sao?
Xem đáp án
false. NaN là "không xác định" — IEEE 754 rule: NaN không bằng bất kỳ thứ gì, kể cả chính nó. Check NaN: Number.isNaN(x).
Khác nhau giữa x || 'default' và x ?? 'default'?
Xem đáp án
|| trả vế phải khi vế trái falsy (0, '', false, null, undefined, NaN). ?? trả vế phải chỉ khi null hoặc undefined.
Ví dụ: 0 || 5 === 5 nhưng 0 ?? 5 === 0. Khi 0/'' là giá trị hợp lệ, dùng ??.
Khi nào nên dùng == thay ===?
Xem đáp án
Gần như không bao giờ. Một ngoại lệ hợp lý: x == null để check cả null và undefined cùng lúc (loose equality giữa null và undefined là true). Nhưng nhiều code base vẫn cấm tuyệt đối qua ESLint rule eqeqeq.
Tổng kết
Sau chương 2, bạn nên đã master:
- 3 keyword khai báo: default
const, fallbacklet, tránhvar. - 7 primitive types + 1 Object.
typeof nulllà gotcha. - null vs undefined: undefined là JS sinh, null là explicit.
- Truthy/falsy: 6 falsy values, mọi thứ khác truthy (kể cả
[],{}). - Type coercion: implicit
+/==/ifphức tạp; ưu tiên explicitNumber()/String()/Boolean(). - NaN là number, không bằng chính nó — check bằng
Number.isNaN. ?.và??: tools modern để handle null an toàn.
Kết nối
- Chương 3 (Functions) — đào sâu hoisting và TDZ qua function declaration.
- Chương 4 (Objects) — phân biệt primitive vs object reference rõ ràng.
- Chương 9 (TS Types) — type của TypeScript đối chiếu với type runtime JS.
- Dart Chương 2 — đối chiếu sound null safety Dart vs strict mode TS.