Buổi 11: JavaScript căn bản (2): mảng, điều kiện, vòng lặp, xử lý lỗi, đối tượng
Hãy tưởng tượng bạn đang làm trang web bán hàng cho một cửa hàng nhỏ. Trang cần lưu danh sách sản phẩm, quyết định hiện nhãn "Hết hàng" hay "Còn hàng", lặp qua từng sản phẩm để dựng bảng giá, báo lỗi thân thiện khi khách nhập sai, và mô tả mỗi sản phẩm như một "vật thể" có tên, giá, mô tả. Tất cả những việc đó đều dựa trên năm công cụ của buổi hôm nay: mảng (Array), câu điều kiện (if-else, switch), vòng lặp (while, do-while, for), xử lý lỗi (try-catch-finally) và đối tượng (Object, class). Buổi trước bạn đã có biến, kiểu dữ liệu, toán tử và hàm; buổi này ghép chúng lại thành những chương trình thực sự có "đầu óc".
Mục tiêu buổi học
- Sau buổi này bạn có thể khai báo, truy cập, thêm/xóa, duyệt (
for,forEach,map,filter,reduce,every) và sắp xếp (sortvới hàm so sánh) mảng. - Sau buổi này bạn có thể viết câu điều kiện
if / else if / elsevàswitch(cóbreak,default, gom nhiềucase) để điều khiển luồng chương trình. - Sau buổi này bạn có thể chọn đúng vòng lặp
while,do-while,for,for/in,for/ofvà dùngbreak,continue, nhãn (label) để điều khiển vòng lặp. - Sau buổi này bạn có thể bắt và xử lý lỗi bằng
try ... catch ... finally, chủ động ném lỗi bằngthrow new Error(...). - Sau buổi này bạn có thể tạo đối tượng bằng
{}, hàm khởi tạo,class; dùngprototype,extends/super, phương thứcstatic,getter/setter.
Bản đồ buổi học
| Phần | Nội dung | Thời lượng gợi ý |
|---|---|---|
| 1. Mảng | Khái niệm, phương thức, duyệt mảng, sắp xếp | 30 phút |
| 2. Câu điều kiện | if-else, else if, switch |
25 phút |
| 3. Vòng lặp | while, do-while, for, for/in, for/of, break/continue |
25 phút |
| 4. Xử lý lỗi | try-catch, throw, finally |
20 phút |
| 5. Đối tượng | {}, hàm khởi tạo, class, kế thừa, static, getter/setter |
30 phút |
| Thực hành theo giáo trình | Bài 5.1, 5.2, 5.3, bài tự thực hành 5.4, 5.5 | 25 phút |
| Ôn tập, tổng kết | Câu hỏi ôn tập, sai lầm thường gặp | 25 phút |
Lưu ý: Thực hành (ví dụ chạy thử và các bài trong ngân hàng bài tập) chiếm khoảng một nửa thời lượng. Hãy bấm "Chạy thử" ở từng ví dụ, sửa mã rồi chạy lại: đó là cách nhớ lâu nhất.
1. Mảng
Giáo trình: Chương 5 — mục "Mảng" (Khái niệm về mảng; Phương thức làm việc với mảng; Duyệt mảng; Sắp xếp mảng).
1.1. Khái niệm về mảng
Mảng (Array) được dùng để lưu trữ nhiều giá trị vào một biến. Khai báo mảng bằng dấu ngoặc vuông [] chứa các phần tử, hoặc dùng new Array. Để truy cập từng phần tử, dùng chỉ số trong ngoặc vuông; phần tử đầu tiên có chỉ số 0. Các phần tử của mảng có thể thuộc nhiều kiểu khác nhau và cũng có thể là một đối tượng, một mảng khác hoặc một hàm.
➕➖ Thêm / bớt ở hai đầu
push("Quảng Ninh")→ 4pop()→ "Quảng Ninh"unshift("Tuyên Quang")→ 4shift()→ "Tuyên Quang"✂️ Cắt, chèn, xóa tại vị trí bất kỳ
slice(1, 3)→ [20, 30]splice(1, 2)→ [20, 30]splice(1, 0, 20, 30)→ []🔍 Tìm kiếm trong mảng
indexOf("Hưng Yên")indexOf("Huế")↕️ Sắp xếp, đảo ngược, nối thành chuỗi
sort((a, b) => a - b)reverse()join("-")→ chuỗi⚠ sort() mặc định so sánh theo CHUỖI: [10, 9, 1].sort() cho [1, 10, 9]. Với số, luôn truyền hàm so sánh.
🔁 Duyệt mảng và biến đổi
map(x => x * 2)mảng MỚI, cùng độ dàifilter(x => x > 2)mảng MỚI, chỉ giữ phần tử đạtreduce((acc, x) => acc + x, 0)MỘT giá trị duy nhấtforEach(x => console.log(x))chỉ duyệt, trả undefinedChỉ số bắt đầu từ 0. Các phương thức màu đỏ làm THAY ĐỔI mảng gốc (sửa tại chỗ); màu xanh lá chỉ đọc hoặc trả về mảng/giá trị MỚI, mảng gốc giữ nguyên.
Hình trên tóm tắt các phương thức mảng bạn sẽ gặp trong phần này; hãy nhìn nhóm nào "làm thay đổi mảng gốc" và nhóm nào "trả về mảng mới".
var thanhpho = ["Hà Nội", "Thái Bình", "Hưng Yên"];
var nghenghiep = new Array("Giáo viên", "Bác sĩ", "Doanh nhân");
// Gán vào chỉ số mới: mảng tự dài ra
thanhpho[3] = "Quảng Ninh";
thanhpho[4] = 29; // phần tử có thể khác kiểu
thanhpho[5] = () => "Vĩnh Phúc"; // phần tử có thể là một hàm
console.log("Độ dài:", thanhpho.length);
console.log("Phần tử đầu:", thanhpho[0]);
console.log("Phần tử chỉ số 3:", thanhpho[3]);
console.log("Kiểu của phần tử 5:", typeof thanhpho[5]);
console.log("Gọi hàm trong mảng:", thanhpho[5]());
console.log("Nghề nghiệp:", nghenghiep);Duyệt các phần tử của mảng bằng vòng lặp for (nhớ rằng chỉ số bắt đầu từ 0), hoặc dùng phương thức forEach():
var thanhpho = ["Hà Nội", "Thái Bình", "Hưng Yên"];
var nghenghiep = new Array("Giáo viên", "Bác sĩ", "Doanh nhân");
for (let i = 0; i < thanhpho.length; i++) {
console.log(i + ": " + thanhpho[i]);
}
nghenghiep.forEach((item) => console.log(item));Lưu ý: Mảng là một dạng đặc biệt của đối tượng nên
typeoftrả về"object", chứ không phải"array". Muốn kiểm tra có đúng là mảng hay không, dùngArray.isArray(x).
var a = ["x", "y"];
console.log(typeof a); // object
console.log(Array.isArray(a)); // true
console.log(Array.isArray({})); // false🧰 Phòng thí nghiệm phương thức mảng
mỗi lần Chạy áp lên mảng hiện tại, kết quả cộng dồnarr.push(…)Chọn một phương thức, chỉnh tham số rồi bấm ▶ Chạy để xem mảng trước/sau và giá trị trả về.
Khung mô phỏng trên cho bạn chọn một phương thức mảng, bấm áp dụng và xem mảng thay đổi ra sao. Hãy thử push, pop, shift, unshift và để ý chỉ số các phần tử còn lại dịch chuyển thế nào.
1.2. Phương thức làm việc với mảng
Mảng cũng là một đối tượng nên có rất nhiều phương thức có sẵn. toString() chuyển mảng thành chuỗi; join() cũng chuyển thành chuỗi nhưng cho phép chỉ định ký tự phân cách giữa các phần tử.
var thanhpho = ["Hà Nội", "Thái Bình", "Hưng Yên"];
console.log(thanhpho.toString()); // Hà Nội,Thái Bình,Hưng Yên
console.log(thanhpho.join("-")); // Hà Nội-Thái Bình-Hưng Yênpush() thêm phần tử vào cuối mảng; pop() xóa phần tử cuối và trả về chính phần tử đó; shift() xóa phần tử đầu; unshift() thêm phần tử vào đầu.
var thanhpho = ["Hà Nội", "Thái Bình", "Hưng Yên"];
console.log(thanhpho);
thanhpho.push("Quảng Ninh"); // thêm vào cuối
console.log(thanhpho);
var daXoa = thanhpho.pop(); // xóa phần tử cuối
console.log("Đã xóa:", daXoa, thanhpho);
thanhpho.shift(); // xóa phần tử đầu
console.log(thanhpho);
thanhpho.unshift("Tuyên Quang"); // thêm vào đầu
console.log(thanhpho);Cảnh báo: Để xóa phần tử có thể dùng lệnh
delete, nhưngdeletetạo ra lỗ hổng (hole): vị trí đó không còn dữ liệu trong khi độ dài mảng vẫn giữ nguyên. Muốn xóa, hãy dùngshift()hoặcpop()(chúng cũng trả về giá trị vừa xóa).
var a = ["A", "B", "C"];
delete a[1];
console.log(a.length); // 3 -> độ dài không đổi
console.log(a[1]); // undefined -> có "lỗ hổng"
var b = ["A", "B", "C"];
b.splice(1, 1); // xóa 1 phần tử tại vị trí 1
console.log(b, b.length);Ngoài ra còn nhiều phương thức hữu ích khác: splice() thêm, thay thế hoặc xóa phần tử tại vị trí bất kỳ; concat() ghép một mảng vào mảng khác; slice() cắt một phần của mảng sang một mảng mới.
var a = [1, 2, 3, 4, 5];
var b = a.slice(1, 3); // lấy từ chỉ số 1 đến trước chỉ số 3
console.log(b, a); // a không đổi
var c = a.concat([6, 7]); // ghép mảng
console.log(c);
a.splice(1, 2, "x", "y", "z"); // tại vị trí 1: xóa 2 phần tử, chèn 3 phần tử
console.log(a);Mở rộng:
push,pop,shift,unshift,splice,sort,reverselàm thay đổi mảng gốc; cònslice,concat,map,filter,joinkhông làm thay đổi mảng gốc mà trả về giá trị/mảng mới. Phân biệt hai nhóm này giúp tránh nhiều lỗi khó hiểu.
1.3. Duyệt mảng
Ngoài for và forEach, JavaScript cho phép duyệt qua từng phần tử và làm thêm một việc khác trong khi duyệt. map() duyệt mảng và tạo ra một mảng mới (cùng số phần tử, mỗi phần tử đã được biến đổi); filter() cũng tạo mảng mới nhưng chỉ giữ các phần tử phù hợp; reduce() duyệt và gộp mảng thành một giá trị duy nhất; every() kiểm tra toàn bộ phần tử có thỏa một điều kiện hay không.
var thanhpho = ["Hà Nội", "Thái Bình", "Hưng Yên"];
// map: đánh số thứ tự
var thanhpho1 = thanhpho.map((value, index) => index + 1 + ". " + value);
console.log(thanhpho1); // [ '1. Hà Nội', '2. Thái Bình', '3. Hưng Yên' ]
// filter: chỉ giữ phần tử có chữ "i"
var thanhpho2 = thanhpho.filter((value, index) => value.includes("i"));
console.log(thanhpho2); // [ 'Hà Nội', 'Thái Bình' ]
// reduce: gộp thành một chuỗi
var thanhpho3 = thanhpho.reduce((total, value, index) => total + "|" + value);
console.log(thanhpho3); // Hà Nội|Thái Bình|Hưng Yên
// every: mọi phần tử đều có độ dài >= 6?
var thanhpho4 = thanhpho.every((value) => value.length >= 6);
console.log(thanhpho4); // trueVới số liệu, reduce thường kèm giá trị khởi tạo (đối số thứ hai) để tính tổng, đếm hay tìm lớn nhất:
var gia = [120, 80, 250, 40];
var tong = gia.reduce((acc, v) => acc + v, 0);
console.log("Tổng:", tong);
var lonNhat = gia.reduce((m, v) => (v > m ? v : m), gia[0]);
console.log("Lớn nhất:", lonNhat);
console.log("Giảm 10%:", gia.map((v) => v * 0.9));
console.log("Trên 100:", gia.filter((v) => v > 100));Mẹo: Nhớ nhanh:
map= "biến đổi từng phần tử" (số phần tử không đổi);filter= "lọc bớt" (có thể ít hơn);reduce= "gộp lại thành một";every= "tất cả đều đúng?". Hàm truyền vào nhận(value, index)— giá trị rồi chỉ số.
1.4. Sắp xếp mảng
Phương thức sort() sắp xếp các phần tử trong mảng. Mặc định nó so sánh như chuỗi, nên chỉ có tác dụng khi các phần tử so sánh được với nhau theo cách đó.
var thanhpho = ["Hà Nội", "Thái Bình", "Hưng Yên"];
thanhpho.sort();
console.log(thanhpho); // [ 'Hà Nội', 'Hưng Yên', 'Thái Bình' ]Khi các phần tử là đối tượng (hoặc là số), hệ thống không biết cách so sánh, vì vậy ta cần cung cấp một hàm so sánh: hàm nhận hai phần tử a, b; trả về số âm nếu a đứng trước b, số dương nếu a đứng sau, 0 nếu bằng nhau. Ví dụ sắp xếp danh sách sinh viên theo tuổi:
var dsSinhvien = [
{ ten: "Anh", tuoi: 20 },
{ ten: "Phuong", tuoi: 21 },
{ ten: "Minh", tuoi: 19 },
{ ten: "Binh", tuoi: 20 }
];
dsSinhvien.sort(function (a, b) { return a.tuoi - b.tuoi; });
console.log(dsSinhvien);Muốn sắp xếp theo chiều ngược lại, có thể sửa hàm so sánh thành b.tuoi - a.tuoi, hoặc dùng reverse() để đảo thứ tự sau khi sắp xếp.
var so = [10, 9, 100, 1];
so.sort(); // mặc định: so sánh như chuỗi!
console.log(so); // [ 1, 10, 100, 9 ]
so.sort((a, b) => a - b); // tăng dần theo giá trị số
console.log(so); // [ 1, 9, 10, 100 ]
so.sort((a, b) => b - a); // giảm dần
console.log(so); // [ 100, 10, 9, 1 ]
so.sort((a, b) => a - b).reverse();
console.log(so); // đảo ngược sau khi sắp tăngCảnh báo:
[10, 9, 100, 1].sort()cho[1, 10, 100, 9]vì so sánh theo chuỗi. Với số luôn truyền hàm so sánh(a, b) => a - b.
Để sắp xếp theo nhiều tiêu chí (ví dụ điểm giảm dần, trùng điểm thì theo tên), hàm so sánh trả về hiệu của tiêu chí chính, nếu bằng 0 thì xét tiêu chí phụ:
var ds = [
{ ten: "Lan", diem: 8 },
{ ten: "An", diem: 9 },
{ ten: "Bình", diem: 8 }
];
ds.sort((a, b) => b.diem - a.diem || a.ten.localeCompare(b.ten));
ds.forEach((s) => console.log(s.ten, s.diem));Giáo trình: Mục này có liên kết tới trang minh họa "Mảng và phương thức" trên smartdoc của Khoa; khung
js-array-labở trên đóng vai trò tương tự ngay trong bài.
Vừa học xong khai báo, phương thức, duyệt và sắp xếp mảng, hãy làm các bài sau để quen dùng filter, reduce, sort với hàm so sánh trên dữ liệu thật.
2. Câu điều kiện
Giáo trình: Chương 5 — mục "Câu điều kiện" (Câu lệnh if-else; Câu lệnh switch).
Trong JavaScript có hai loại câu điều kiện:
- Câu lệnh
if/else: chỉ định khối mã sẽ được thực thi tùy theo một điều kiện là đúng hay sai. - Câu lệnh
switch: lựa chọn thực thi một trong nhiều khối mã tùy theo giá trị của một biểu thức.
2.1. Câu lệnh if-else
Dùng if để chỉ định một khối mã sẽ được thực thi nếu điều kiện đúng; dùng else để chỉ định khối mã thực thi nếu điều kiện sai.
if (điều kiện) {
// Khối lệnh sẽ thực hiện khi điều kiện đúng
} else {
// Khối lệnh sẽ thực hiện khi điều kiện sai
}Ví dụ: hiển thị lời nhắn "Bạn đủ điều kiện bầu cử" nếu tuổi từ 18 trở lên.
let tuoi = 20;
if (tuoi >= 18) {
console.log("Bạn đủ điều kiện bầu cử");
} else {
console.log("Bạn CHƯA đủ điều kiện bầu cử");
}Hãy đổi tuoi thành 16 rồi chạy lại để thấy nhánh else.
Dùng else if để chỉ định một điều kiện mới nếu điều kiện đầu tiên sai:
if (điều kiện 1) {
// Khi điều kiện 1 đúng
} else if (điều kiện 2) {
// Khi điều kiện 2 đúng
} else {
// Khi cả hai điều kiện trên đều sai
}Ví dụ của giáo trình: trước 11 giờ chào "Chào buổi sáng"; sau 19 giờ chào "Chào buổi tối"; các thời điểm khác chúc "Chúc một ngày tốt lành". Giáo trình lấy giờ hiện tại bằng new Date().getHours() (giá trị từ 0 đến 23). Để kết quả chạy thử tất định, ta đặt giờ cho trước và đóng gói vào hàm:
function loiChao(hour) {
if (hour < 11) {
return "Chào buổi sáng";
} else if (hour > 19) {
return "Chào buổi tối";
} else {
return "Chúc một ngày tốt lành";
}
}
// Với giờ thật: let hour = new Date().getHours();
[8, 14, 21].forEach((h) => console.log(h + " giờ -> " + loiChao(h)));Lưu ý: Điều kiện trong
ifđược chuyển về đúng/sai (truthy/falsy). Các giá trị falsy gồmfalse,0,"",null,undefined,NaN; mọi giá trị khác là truthy. Các nhánh được xét từ trên xuống, dừng ở nhánh đúng đầu tiên, vì vậy hãy xếp điều kiện hẹp trước, rộng sau.
function xepLoai(diem) {
if (diem >= 8.5) return "Giỏi";
else if (diem >= 7) return "Khá";
else if (diem >= 5) return "Trung bình";
else return "Yếu";
}
[9, 7.5, 5, 3].forEach((d) => console.log(d, "->", xepLoai(d)));
// Phép so sánh: nhớ dùng === (so cả kiểu), không nhầm = (gán) với == / ===
let x = 5;
if (x === 5) console.log("x bằng 5");
if (x !== 6) console.log("x khác 6");2.2. Câu lệnh switch
Lệnh switch thực hiện các hành động khác nhau dựa trên các giá trị khác nhau của một biểu thức:
switch (Biểu thức) {
case x:
// Khối lệnh
break;
case y:
// Khối lệnh
break;
default:
// Khối lệnh
}Giá trị của biểu thức được so khớp (so sánh chặt như ===) với giá trị ở từng case; case nào khớp thì khối lệnh tương ứng được thực hiện. Ví dụ dùng số thứ tự ngày trong tuần để xác định tên ngày (giáo trình dùng new Date().getDay(); ở đây ta đặt số ngày cho trước):
function tenThu(n) {
let day;
switch (n) {
case 0: day = "Chủ nhật"; break;
case 1: day = "Thứ Hai"; break;
case 2: day = "Thứ Ba"; break;
case 3: day = "Thứ Tư"; break;
case 4: day = "Thứ Năm"; break;
case 5: day = "Thứ Sáu"; break;
case 6: day = "Thứ Bảy"; break;
default: day = "Không phải ngày trong tuần";
}
return day;
}
// Với ngày thật: tenThu(new Date().getDay())
[0, 3, 6, 9].forEach((n) => console.log(n + " -> " + tenThu(n)));Khi JavaScript gặp lệnh break, nó thoát ra khỏi khối switch. Nếu bỏ qua break, khối lệnh của case tiếp theo vẫn được thực thi (hiện tượng fall-through) ngay cả khi giá trị không khớp với case đó. Từ khóa default chỉ định mã chạy nếu không có trường hợp nào phù hợp; default không bắt buộc đặt ở cuối.
// Quên break -> chạy "rơi" xuống các case sau
let n = 1;
switch (n) {
case 1: console.log("case 1");
case 2: console.log("case 2 (chạy luôn vì thiếu break)");
case 3: console.log("case 3 (chạy luôn)"); break;
case 4: console.log("case 4 (không chạy)");
}Cảnh báo: Quên
breaklà lỗi rất hay gặp khi viếtswitch. Chỉ bỏbreakkhi cố ý gom nhiềucase.
Đôi khi ta muốn nhiều trường hợp cùng thực hiện một mã lệnh. Khi đó dùng các case liên tiếp nhau. Trong ví dụ dưới đây, case 1 đến case 5 dùng chung một khối mã, còn case 0 và case 6 dùng chung một khối mã khác:
function nhacNho(ngay) {
let text = "";
switch (ngay) {
case 0:
case 6: // Các case có thể viết trên các dòng
text = "Ngày cuối tuần, nghỉ ngơi thôi";
break;
case 1: case 2: case 3: case 4: case 5: // hoặc gom trên một dòng
text = "Chúc bạn một ngày làm việc hiệu quả";
break;
}
return text;
}
console.log(nhacNho(0));
console.log(nhacNho(3));
console.log(nhacNho(6));Mẹo: Khi chọn theo một giá trị cụ thể (ngày, tháng, mã lệnh) dùng
switchcho dễ đọc; khi chọn theo khoảng giá trị (điểm từ 5 đến 6.9...) dùngif / else if.
Vừa học xong if-else và switch, hãy làm các bài sau để luyện chia nhánh theo khoảng giá trị (xếp loại, BMI, tiền điện bậc thang) và theo giá trị cụ thể (số ngày của tháng với switch gom case).
3. Vòng lặp
Giáo trình: Chương 5 — mục "Vòng lặp" (while; do-while; for; câu lệnh ngắt).
Vòng lặp là cú pháp cho phép thực thi một khối mã nhiều lần. JavaScript hỗ trợ các loại vòng lặp: while, do/while, for, for/in, for/of.
🔁 Vòng lặp chạy từng bước
chọn vòng lặp, thêm break/continue rồi bấm Bước tiếpKhung mô phỏng trên chạy vòng lặp từng bước: bạn thấy giá trị biến đếm, điều kiện đúng/sai và khối lệnh được thực hiện ở mỗi lượt. Hãy bấm từng bước trước khi đọc các ví dụ bên dưới.
3.1. Vòng lặp while
Vòng lặp while thực hiện một khối lệnh miễn là biểu thức được chỉ định còn đúng: kiểm tra điều kiện trước, nếu đúng mới chạy khối lệnh.
while (biểu thức) {
khối lệnh
}Ví dụ: lặp chừng nào biến i còn nhỏ hơn số phần tử của mảng menu:
var menu = ["Trang chủ", "Bài viết", "Dự án", "Đăng ký"];
let i = 0;
while (i < menu.length) {
console.log(menu[i]);
i++;
}Cảnh báo: Quên tăng
i(dòngi++) làm điều kiện luôn đúng, vòng lặp chạy mãi (vòng lặp vô hạn) và treo trình duyệt. Luôn kiểm tra: có lệnh nào làm điều kiện cuối cùng thành sai không?
3.2. Vòng lặp do-while
Vòng lặp do-while hoạt động tương tự while, nhưng thực thi khối lệnh một lần trước rồi mới kiểm tra biểu thức, sau đó lặp lại miễn là biểu thức còn đúng:
do {
khối lệnh
} while (biểu thức);Khác với while (có thể không chạy lần nào nếu điều kiện sai ngay từ đầu), do-while luôn chạy khối lệnh ít nhất một lần.
var menu = ["Trang chủ", "Bài viết", "Dự án", "Đăng ký khóa học"];
let i = 0;
do {
console.log(menu[i]);
i++;
} while (i < menu.length);
// So sánh: điều kiện sai ngay từ đầu
let k = 10;
while (k < 5) { console.log("while: không bao giờ in"); }
do { console.log("do-while: vẫn chạy 1 lần, k =", k); } while (k < 5);3.3. Vòng lặp for
Vòng lặp for thường dùng khi biết trước số lần chạy:
for (lệnh khởi tạo đầu; biểu thức; lệnh cập nhật) {
khối lệnh
}Thứ tự thực hiện: lệnh khởi tạo chạy một lần → kiểm tra biểu thức → nếu đúng, chạy khối lệnh → chạy lệnh cập nhật → quay lại kiểm tra biểu thức... cho tới khi biểu thức sai.
var menu = ["Trang chủ", "Bài viết", "Dự án", "Đăng ký khóa học"];
for (let i = 0; i < menu.length; i++) {
console.log(menu[i]);
}
// Đếm ngược, bước nhảy 2
for (let n = 10; n > 0; n -= 2) console.log("n =", n);Vòng lặp for/in lặp qua các thuộc tính của một đối tượng (biến lặp nhận tên thuộc tính, nên cần dùng đối-tượng[tên] để lấy giá trị):
var sinhvien = {
ho: "Pham",
dem: "Duong",
ten: "Minh",
tuoi: 21,
quequan: "Hai Duong",
lop: "Cong nghe thong tin 62"
};
for (let x in sinhvien) {
console.log(x + " = " + sinhvien[x]);
}Vòng lặp for/of lặp qua các cấu trúc dữ liệu có thể lặp (iterable) như mảng (Array), chuỗi (String), Map, Set, NodeList,... và trả về giá trị từng phần tử:
var menu = ["Trang chủ", "Bài viết", "Dự án", "Đăng ký khóa học"];
for (let item of menu) {
console.log(item);
}
var tieude = "Thiết kế Web";
var kyTu = [];
for (let c of tieude) {
kyTu.push(c);
}
console.log(kyTu.join("|"));Mẹo:
for/incho tên khóa (dùng với đối tượng),for/ofcho giá trị (dùng với mảng, chuỗi). Dùngfor/intrên mảng sẽ nhận về chỉ số dạng chuỗi — hiếm khi là điều bạn muốn.
var arr = ["a", "b"];
for (let k in arr) console.log("for/in:", k, typeof k); // "0" "1" là chuỗi
for (let v of arr) console.log("for/of:", v);3.4. Câu lệnh ngắt
Các câu lệnh break và continue điều khiển vòng lặp theo ý muốn mà không phụ thuộc hoàn toàn vào điều kiện kiểm tra; chúng còn được gọi là câu lệnh ngắt. break kết thúc vòng lặp; continue bỏ qua một lượt lặp.
var menu = "Thiết kế Web";
// break: dừng khi gặp ký tự W
let a = [];
for (let item of menu) {
if (item === "W") break;
a.push(item);
}
console.log("Trước W:", a.join(""));
// continue: bỏ qua dấu cách
let b = [];
for (let item of menu) {
if (item === " ") continue;
b.push(item);
}
console.log("Không dấu cách:", b.join(""));break và continue còn có thể dùng kèm nhãn (label) để nhảy ra khỏi một khối mã, tới câu lệnh được gán nhãn. Nếu dùng break bên trong vòng lặp lồng nhau, nó chỉ thoát ra khỏi vòng lặp trong cùng; để thoát khỏi vòng lặp lồng nhau từ bên trong cần dùng break kèm nhãn.
// Tìm cặp (i, j) đầu tiên có i * j = 12, i và j từ 1 đến 5
let tim = null;
ngoai: for (let i = 1; i <= 5; i++) {
for (let j = 1; j <= 5; j++) {
if (i * j === 12) {
tim = [i, j];
break ngoai; // thoát cả hai vòng lặp
}
}
}
console.log("Cặp tìm thấy:", tim);
// break không nhãn chỉ thoát vòng trong
for (let i = 1; i <= 2; i++) {
for (let j = 1; j <= 3; j++) {
if (j === 2) break;
console.log("i =", i, "j =", j);
}
}Vừa học xong các vòng lặp và câu lệnh ngắt, hãy làm các bài sau để luyện while với điều kiện dừng, break/continue, và vòng lặp lồng nhau trong thuật toán sắp xếp.
4. Xử lý lỗi
Giáo trình: Chương 5 — mục "Xử lý lỗi" (Sử dụng try catch để bắt lỗi; Ném lỗi; Sử dụng finally).
✅ throw new Error("Không hợp lệ")⚠️ throw "Không hợp lệ"⚠️ throw 500Lỗi trong try dừng ngay khối try và nhảy sang catch; finally luôn chạy ở cuối, dù có lỗi hay không. Đối tượng Error mang theo name và message cho biết lỗi gì đã xảy ra.
Sơ đồ trên cho thấy luồng chạy của try / catch / finally: mã trong try chạy trước; nếu có lỗi, nhảy sang catch; cuối cùng finally luôn chạy dù có lỗi hay không. Hãy lần theo từng mũi tên khi đọc các ví dụ dưới đây.
4.1. Sử dụng try catch để bắt lỗi
Trong quá trình thực hiện mã lệnh, nếu một lỗi xuất hiện tại một câu lệnh bất kỳ, chương trình sẽ dừng việc thực thi, các câu lệnh phía dưới không được thực hiện. Ví dụ với hai dòng sau, biến x chưa được khai báo nên phát sinh lỗi, và dòng "Kết thúc" không bao giờ chạy:
console.log(x);
console.log("Kết thúc");
// ReferenceError: x is not definedHệ thống báo một lỗi tham chiếu (ReferenceError): x được sử dụng nhưng chưa được định nghĩa. Để những lỗi phát sinh được xử lý, ta dùng cấu trúc try...catch: lệnh try xác định một khối mã được kiểm tra lỗi trong khi thực thi; lệnh catch khai báo khối lệnh sẽ chạy nếu khối try phát sinh lỗi.
try {
khối lệnh thực thi
} catch (err) {
khối lệnh sẽ được thực hiện nếu khối lệnh thực thi phát sinh lỗi
}try {
console.log(x); // x chưa khai báo -> lỗi
} catch (err) {
console.log(err.name + ": " + err.message);
}
console.log("Kết thúc"); // vẫn được thực hiệnKhi xảy ra lỗi, JavaScript tạo một đối tượng Error với hai thuộc tính: tên (name) và thông báo (message). Các thông tin này giúp nhà phát triển biết lỗi gì đã phát sinh. Nhờ try...catch, các câu lệnh phía dưới vẫn được thực hiện.
Lưu ý: Các tên lỗi thường gặp:
ReferenceError(dùng biến chưa khai báo),TypeError(gọi thứ không phải hàm, đọc thuộc tính củaundefined),SyntaxError(sai cú pháp, ví dụJSON.parsechuỗi hỏng),RangeError(giá trị ngoài phạm vi cho phép).
function thu(tenLoi, f) {
try { f(); } catch (e) { console.log(tenLoi + " -> " + e.name); }
}
thu("biến chưa khai báo", () => console.log(chuaCo));
thu("gọi không phải hàm", () => { let s = "abc"; s(); });
thu("đọc thuộc tính của null", () => { let o = null; console.log(o.ten); });
thu("JSON hỏng", () => JSON.parse("{ten:}"));
thu("độ dài mảng âm", () => new Array(-1));4.2. Ném lỗi
Câu lệnh throw cho phép tạo một lỗi. Thông thường lỗi tự phát sinh khi chương trình hoạt động, nhưng lập trình viên cũng có thể chủ động ném lỗi để điều khiển luồng chương trình. Một lỗi ném ra có thể là một chuỗi, một số hoặc một đối tượng; tuy nhiên nên ném một đối tượng Error để giữ được tên lỗi, thông báo lỗi và vết gọi hàm (stack trace).
throw "Không hợp lệ"; // Ném một chuỗi
throw 500; // Ném một số
throw new Error("Không hợp lệ"); // Ném đối tượng Error (cách được khuyến nghị)Nếu dùng throw cùng với try và catch, ta kiểm soát được luồng chương trình và tạo thông báo lỗi tùy chỉnh:
function chiaSo(a, b) {
if (typeof a !== "number" || typeof b !== "number") {
throw new TypeError("Cả hai đối số phải là số");
}
if (b === 0) {
throw new Error("Không thể chia cho 0");
}
return a / b;
}
[[10, 2], [5, 0], [4, "x"]].forEach(([a, b]) => {
try {
console.log(a + " / " + b + " = " + chiaSo(a, b));
} catch (err) {
console.log("Lỗi (" + err.name + "): " + err.message);
}
});
// Ném chuỗi/số vẫn bắt được, nhưng không có name/message
try { throw "Không hợp lệ"; } catch (e) { console.log(typeof e, e, e.name); }
try { throw 500; } catch (e) { console.log(typeof e, e); }Mẹo: Hãy kiểm tra dữ liệu đầu vào ở đầu hàm và
throw new Error(...)với thông báo rõ ràng (ví dụ "Chiều dài phải là số dương"); phía gọi hàm dùngtry...catchđể hiện thông báo thân thiện cho người dùng.
4.3. Sử dụng finally
Câu lệnh finally cho phép tạo cấu trúc try...catch...finally. Các câu lệnh trong khối finally luôn được thực hiện, dù khối try có phát sinh lỗi hay không.
try {
khối lệnh thực thi
} catch (err) {
khối lệnh sẽ được thực hiện nếu khối lệnh thực thi phát sinh lỗi
} finally {
khối lệnh sẽ luôn được thực hiện
}Để hiểu rõ hơn, xét ví dụ của giáo trình. Trong đoạn mã sau, chữ "Kết thúc" sẽ không được hiển thị vì có lỗi xảy ra ngay trong khối catch (do lệnh throw 4 tạo ra) và không ai bắt lỗi này:
try {
console.log(x);
} catch (err) {
throw 4;
}
console.log("Kết thúc"); // không bao giờ chạyĐể đảm bảo lệnh console.log("Kết thúc") được thực thi, đặt nó vào khối finally:
try {
console.log(x);
} catch (err) {
throw 4;
} finally {
console.log("Kết thúc"); // luôn chạy
}Đoạn mã trên vẫn kết thúc bằng lỗi chưa bắt (do throw 4), nhưng chữ "Kết thúc" đã được in trước đó. Ví dụ chạy được dưới đây bọc thêm một try...catch bên ngoài để quan sát đủ thứ tự:
try {
try {
console.log(x); // lỗi ReferenceError
} catch (err) {
console.log("catch trong:", err.name);
throw 4; // lỗi mới trong catch
} finally {
console.log("Kết thúc (finally luôn chạy)");
}
} catch (e) {
console.log("catch ngoài bắt được:", e);
}
// finally chạy cả khi KHÔNG có lỗi
try {
console.log("không có lỗi");
} catch (err) {
console.log("không chạy");
} finally {
console.log("finally vẫn chạy");
}Giáo trình: Trong bản in của giáo trình, hai khối mã ví dụ ở mục này giống hệt nhau (khối thứ hai quên dòng
finally). Ở đây khối thứ hai đã được sửa đúng theo lời giải thích: dùngfinallyđể chữ "Kết thúc" luôn được in.
finally thường dùng để "dọn dẹp": tắt biểu tượng đang tải, đóng kết nối, trả tài nguyên — việc luôn phải làm dù thành công hay thất bại:
function taiDuLieu(thanhCong) {
let trangThai = "đang tải...";
console.log(trangThai);
try {
if (!thanhCong) throw new Error("Mất kết nối");
return "dữ liệu đã tải";
} catch (e) {
return "lỗi: " + e.message;
} finally {
console.log("đã tắt biểu tượng đang tải");
}
}
console.log(taiDuLieu(true));
console.log(taiDuLieu(false));Lưu ý: Ngay cả khi
tryhoặccatchcóreturn, khốifinallyvẫn chạy trước khi hàm thật sự trả về (xem hai lần gọi ở trên: dòng "đã tắt biểu tượng đang tải" xuất hiện trước kết quả).
Vừa học xong try-catch-finally và throw, hãy làm các bài sau để ném lỗi đúng chỗ (chia cho 0, ngăn xếp rỗng) và bắt lỗi để chương trình không bị dừng.
5. Đối tượng
Giáo trình: Chương 5 — mục "Đối tượng" (Tạo đối tượng bằng {}; bằng phương thức khởi tạo; bằng lớp (class); Tính chất kế thừa; Phương thức tĩnh; Phương thức getter và setter).
Các cách để tạo đối tượng trong JavaScript:
- Sử dụng
{}; - Sử dụng hàm khởi tạo (constructor);
- Sử dụng
Object.create(); - Sử dụng
new Object; - Sử dụng
new+ tên lớp.
1let a = 5;2let b = a;3b = 9;4let sv1 = { ten: "An", tuoi: 20 };5let sv2 = sv1;6sv2.tuoi = 21;7let sv3 = { ...sv1 };8sv3.tuoi = 30;
Mỗi bước 3 giây: dòng mã đang chạy được tô sáng, sơ đồ bên cạnh dựng dần.
Gán biến nguyên thủy chép GIÁ TRỊ nên hai biến độc lập. Gán biến đối tượng chỉ chép THAM CHIẾU: sv1 và sv2 cùng trỏ một đối tượng trên heap, sửa qua biến nào cũng thấy ở biến kia.
Hình trên nhắc một điều quan trọng: biến chứa đối tượng giữ tham chiếu tới đối tượng chứ không giữ bản sao. Hãy ghi nhớ khi gán đối tượng sang biến khác (mục 5.1 có ví dụ).
5.1. Tạo đối tượng bằng {}
Đối tượng là tập hợp các cặp tên thuộc tính : giá trị (thuộc tính hàm gọi là phương thức):
let me = {
ten: "Phạm Dương Minh",
email: "[email protected]",
diachi: "207 Giải Phóng",
tuoi: 22
};
console.log(me);
console.log(typeof me);Có thể khai báo đối tượng rỗng rồi thêm từng thuộc tính:
let me = {};
me.ten = "Phạm Dương Minh";
me.email = "[email protected]";
me.diachi = "207 Giải Phóng";
me.tuoi = 22;
console.log(me);Các thuộc tính được truy cập bằng toán tử chấm . hoặc dấu ngoặc vuông:
tên-đối-tượng.tên-thuộc-tính
tên-đối-tượng[tên-thuộc-tính]Với cách thứ hai ta có thể đặt một biểu thức tính toán (hoặc biến chứa tên thuộc tính) vào trong ngoặc vuông, điều mà cách thứ nhất không làm được. Ví dụ duyệt các thuộc tính bằng vòng lặp for/in, mỗi lần lặp biến nhận tên thuộc tính, khi đó cần cách thứ hai để lấy giá trị:
let me = { ten: "Phạm Dương Minh", email: "[email protected]", diachi: "207 Giải Phóng", tuoi: 22 };
console.log(me.ten); // cách 1: dấu chấm
console.log(me["email"]); // cách 2: ngoặc vuông
let tenThuocTinh = "tuoi";
console.log(me[tenThuocTinh]); // tên thuộc tính nằm trong biến
console.log(me.tenThuocTinh); // undefined: tìm thuộc tính tên "tenThuocTinh"
for (let i in me) {
console.log(i + ": " + me[i]);
}🧩 Đối tượng: thuộc tính và tham chiếu
thêm/sửa/xóa thuộc tính, truy cập, và sao chép đối tượng// dạng JSON { "ten": "Phạm Dương Minh", "email": "[email protected]", "tuoi": 22 }
Khung mô phỏng trên cho phép thêm/xóa thuộc tính và gán đối tượng sang biến khác để thấy tham chiếu. Hãy thử sửa thuộc tính qua biến thứ hai và xem biến thứ nhất có đổi theo không.
Thêm thuộc tính chỉ cần gán giá trị vào thuộc tính mới. Thêm phương thức làm tương tự; chú ý trong phương thức muốn truy cập thuộc tính của chính đối tượng thì dùng từ khóa this. Để xóa thuộc tính hoặc phương thức, dùng delete:
let me = { ten: "Phạm Dương Minh", email: "[email protected]", tuoi: 22 };
me.kynang = "JavaScript, Java, Node.js, Web"; // thêm thuộc tính
me.hienthongtin = function () { // thêm phương thức
console.log(this.ten + " : " + this.email);
};
me.hienthongtin();
delete me.hienthongtin; // xóa phương thức
console.log(typeof me.hienthongtin); // undefined
try {
me.hienthongtin(); // không còn hiệu lực
} catch (e) {
console.log(e.name + ": " + e.message);
}Lưu ý: Đối tượng là kiểu tham chiếu.
let b = akhông sao chép đối tượng, chỉ chobtrỏ tới cùng đối tượng: sửa quabthìacũng thấy đổi. Muốn bản sao nông dùng{ ...a }hoặcObject.assign({}, a).
let a = { ten: "An", tuoi: 20 };
let b = a; // cùng tham chiếu
b.tuoi = 99;
console.log("a.tuoi =", a.tuoi); // 99
let c = { ...a }; // sao chép nông
c.tuoi = 1;
console.log("a.tuoi =", a.tuoi, "| c.tuoi =", c.tuoi);
console.log(a === b, a === c); // true false5.2. Tạo đối tượng bằng phương thức khởi tạo
Ngoài {}, ta có thể tạo đối tượng bằng hàm khởi tạo (constructor function) — một hàm (thường viết hoa chữ đầu) gán các thuộc tính cho this, gọi bằng new. Hàm khởi tạo User đóng vai trò như một lớp, từ đó tạo ra nhiều đối tượng khác nhau:
function User(ten, email, diachi, tuoi) {
this.ten = ten;
this.email = email;
this.diachi = diachi;
this.tuoi = tuoi;
this.hienthongtin = function () {
console.log(this.ten + " : " + this.email);
};
}
let me = new User("Phạm Dương Minh", "[email protected]", "207 Giải Phóng", 22);
let ban = new User("Lê Thu Hà", "[email protected]", "Hà Nội", 21);
me.hienthongtin();
ban.hienthongtin();Để thêm một thuộc tính hoặc phương thức cho riêng đối tượng me, chỉ cần gán giá trị/hàm vào thuộc tính đó. Tuy nhiên cách này chỉ đối tượng me có thuộc tính mới. Muốn thêm cho mọi đối tượng của lớp User, dùng prototype:
function User(ten, tuoi) {
this.ten = ten;
this.tuoi = tuoi;
}
let me = new User("Minh", 22);
let ban = new User("Hà", 15);
// Chỉ me có
me.dienthoai = "02939392121";
console.log(me.dienthoai, ban.dienthoai); // 02939392121 undefined
// Thêm cho mọi đối tượng tạo bởi User
User.prototype.quoctich = "Việt Nam";
User.prototype.kiemtra = function () {
if (this.tuoi > 18) return true;
else return false;
};
console.log(me.quoctich, ban.quoctich);
console.log(me.kiemtra(), ban.kiemtra());Khi đó toàn bộ các đối tượng được tạo ra bởi User (kể cả những đối tượng tạo từ trước) đều có thuộc tính quoctich và phương thức kiemtra().
Mở rộng: Đặt phương thức trong
prototypetiết kiệm bộ nhớ hơn gánthis.hienthongtin = function...trong hàm khởi tạo, vì mọi đối tượng dùng chung một bản của hàm thay vì mỗi đối tượng giữ một bản riêng.
5.3. Tạo đối tượng bằng lớp (class)
Từ khóa class định nghĩa lớp, sau đó dùng lớp để tạo đối tượng bằng new. Cú pháp: class Tên-lớp { constructor() { ... } }; các phương thức viết trực tiếp trong thân lớp.
class User {
constructor(ten, email, diachi, tuoi) {
this.ten = ten;
this.email = email;
this.diachi = diachi;
this.tuoi = tuoi;
}
hienthongtin() {
console.log(this.ten + " : " + this.email);
}
}
let me = new User("Phạm Dương Minh", "[email protected]", "207 Giải Phóng", 22);
me.hienthongtin();
console.log(me instanceof User, typeof User);Cảnh báo: Lớp phải được khai báo trước khi tạo đối tượng: khai báo
classtuy cũng được nâng (hoisting) nhưng nằm trong vùng chết tạm thời (TDZ) nên không dùng được trước dòng khai báo.
try {
let a = new Xe("Ford"); // dùng trước khi khai báo
} catch (e) {
console.log(e.name + ": " + e.message);
}
class Xe {
constructor(ten) { this.ten = ten; }
}
console.log(new Xe("Ford").ten); // sau khai báo thì dùng được5.4. Tính chất kế thừa
Kế thừa cho phép một đối tượng/lớp dùng lại thuộc tính và phương thức của đối tượng/lớp khác. Khi tạo đối tượng bằng {}, kế thừa được triển khai qua đối tượng nguyên mẫu (prototype). Cách viết gán thuộc tính __proto__ mà giáo trình nêu (sinhvien.__proto__ = me;) nay đã lỗi thời; mã mới nên dùng Object.create() hoặc Object.setPrototypeOf():
let me = { ten: "Phạm Dương Minh", email: "[email protected]", diachi: "207 Giải Phóng", tuoi: 22 };
// Cách 1: Object.create(nguyên_mẫu)
let sinhvien = Object.create(me);
sinhvien.masv = 1578957;
// Cách 2: Object.setPrototypeOf(đối_tượng, nguyên_mẫu)
let sv2 = { masv: 2020001 };
Object.setPrototypeOf(sv2, me);
console.log(sinhvien.masv, sinhvien.ten); // ten lấy từ nguyên mẫu me
console.log(sv2.masv, sv2.email);
console.log(Object.getPrototypeOf(sinhvien) === me);
console.log(sinhvien.hasOwnProperty("ten")); // false: không phải thuộc tính riêngKhi tạo đối tượng bằng hàm khởi tạo, muốn kế thừa ta dùng phương thức call(): lớp con gọi hàm khởi tạo của lớp cha với this của chính nó. Ví dụ Student kế thừa từ User:
function User(ten, email, diachi, tuoi) {
this.ten = ten;
this.email = email;
this.diachi = diachi;
this.tuoi = tuoi;
this.hienthongtin = function () {
console.log(this.ten + " : " + this.email);
};
}
function Student(ten, email, diachi, tuoi, masv) {
User.call(this, ten, email, diachi, tuoi); // Kế thừa
this.masv = masv;
}
const sinhvien = new Student("Phạm Dương Minh", "[email protected]", "207 Giải Phóng", 22, 1578957);
sinhvien.hienthongtin(); // dùng phương thức của User
console.log(sinhvien.masv);Khi tạo đối tượng bằng khai báo class, kế thừa dùng từ khóa extends. Trong lớp con, gọi hàm khởi tạo của lớp cha bằng super(...) (phải gọi trước khi dùng this) và gọi phương thức của lớp cha bằng super.tênPhươngThức(). Lớp con cũng có thể ghi đè (override) phương thức hay thuộc tính của lớp cha:
class User {
constructor(ten, email, diachi, tuoi) {
this.ten = ten;
this.email = email;
this.diachi = diachi;
this.tuoi = tuoi;
}
hienthongtin() {
console.log(this.ten + " : " + this.email);
}
}
class Student extends User {
constructor(ten, email, diachi, tuoi, masv) {
super(ten, email, diachi, tuoi);
this.masv = masv;
}
hienthongtin() { // ghi đè (override)
super.hienthongtin(); // gọi phương thức cùng tên của lớp cha
console.log(this.masv);
}
}
const sv = new Student("Phạm Dương Minh", "[email protected]", "207 Giải Phóng", 22, 1578957);
sv.hienthongtin();
console.log(sv instanceof Student, sv instanceof User);Ví dụ thực tế: giá bán khác nhau theo loại sản phẩm — lớp con ghi đè một phương thức:
class SanPham {
constructor(ten, gia) { this.ten = ten; this.gia = gia; }
giaBan() { return this.gia; }
mota() { return this.ten + ": " + this.giaBan() + "đ"; }
}
class SanPhamGiamGia extends SanPham {
constructor(ten, gia, giam) { super(ten, gia); this.giam = giam; }
giaBan() { return this.gia * (1 - this.giam); } // ghi đè
}
[new SanPham("Bút", 10000), new SanPhamGiamGia("Vở", 20000, 0.25)]
.forEach((sp) => console.log(sp.mota())); // mota() của lớp cha gọi giaBan() của lớp con5.5. Phương thức tĩnh
Phương thức tĩnh (static method) là phương thức khai báo với từ khóa static. Nó thuộc về lớp, không thuộc về từng đối tượng: không gọi được thông qua đối tượng mà phải gọi thông qua lớp.
class Car {
constructor(name) {
this.name = name;
}
static hello() {
return "Hello!!";
}
}
let myCar = new Car("Ford");
console.log(Car.hello()); // OK
try {
console.log(myCar.hello()); // lỗi
} catch (e) {
console.log(e.name + ": " + e.message);
}Mẹo: Phương thức tĩnh hay dùng cho các hàm tiện ích không cần dữ liệu của một đối tượng cụ thể, ví dụ
Math.max(...),Array.isArray(...),Object.keys(...)mà bạn đã gặp đều là phương thức tĩnh.
5.6. Phương thức getter và setter
Phương thức getter được viết như các phương thức khác nhưng được sử dụng theo cú pháp truy cập thuộc tính (không có dấu ()). Khai báo bằng từ khóa get:
const person = {
firstName: "John",
lastName: "Doe",
language: "en",
get lang() {
return this.language;
}
};
console.log(person.lang); // gọi như thuộc tính, không có ()Phương thức setter khai báo bằng từ khóa set, và khác với các phương thức khác, được gọi thông qua phép gán: ví dụ dưới đây setter lang(language) được gọi khi gán person.lang = "en".
const person = {
firstName: "John",
lastName: "Doe",
language: "",
set lang(language) {
this.language = language;
}
};
person.lang = "en"; // gọi setter qua phép gán
console.log(person.language);Getter/setter còn dùng trong class để kiểm tra dữ liệu trước khi gán:
class HinhChuNhat {
constructor(dai, rong) { this.dai = dai; this.rong = rong; }
get dienTich() { return this.dai * this.rong; } // thuộc tính "tính toán"
set dai(v) {
if (typeof v !== "number" || v <= 0) throw new RangeError("Chiều dài phải là số dương");
this._dai = v;
}
get dai() { return this._dai; }
}
let h = new HinhChuNhat(5, 3);
console.log(h.dienTich);
try { h.dai = -2; } catch (e) { console.log(e.name + ": " + e.message); }
console.log(h.dai);Lưu ý: Trong setter/getter không đặt tên biến lưu trữ trùng với tên thuộc tính (gán
this.dai = vtrongset daisẽ gọi lại chính setter, gây lặp vô tận). Quy ước đặt thêm dấu gạch dưới:_dai.
Vừa học xong đối tượng, lớp, kế thừa và getter/setter, hãy làm các bài sau để mô hình hóa dữ liệu thực tế (tài khoản, hình học) bằng class, extends, super.
Thực hành theo giáo trình
Giáo trình: Chương 5 — mục "Thực hành" (Bài 5.1. Quản lý danh sách công việc; Bài 5.2. Trò chơi đoán số; Bài 5.3. Thông tin sinh viên). Các bài này dùng đúng mảng, câu điều kiện, vòng lặp, đối tượng của buổi hôm nay.
Bài 5.1. Quản lý danh sách công việc
Mục tiêu: viết chương trình quản lý công việc đơn giản: thêm, xóa, hiển thị danh sách. Các bước: (1) khai báo mảng tasks rỗng; (2) viết hàm addTask dùng push; (3) viết hàm removeTask dùng splice(index, 1); (4) viết hàm displayTasks dùng forEach in số thứ tự (index + 1) và nội dung; (5) thêm 3 công việc, hiển thị, xóa một công việc rồi hiển thị lại.
let tasks = [];
function addTask(task) {
tasks.push(task);
}
function removeTask(index) {
tasks.splice(index, 1);
}
function displayTasks() {
console.log("Danh sách công việc:");
tasks.forEach((task, index) => {
console.log(`${index + 1}. ${task}`);
});
}
addTask("Học JavaScript");
addTask("Làm bài tập");
addTask("Đọc sách");
displayTasks();
removeTask(1);
console.log("Sau khi xóa một công việc:");
displayTasks();Mẹo: Thử mở rộng: thêm hàm
doneTask(index)đánh dấu hoàn thành bằng cách đổi công việc thành đối tượng{ten, xong}và dùngfilterđể liệt kê việc chưa xong.
Bài 5.2. Trò chơi đoán số
Mục tiêu: viết chương trình cho đoán một số từ 1 đến 100; chạy thử hàm đoán số với ba lượt đoán thử và một lượt đoán đúng. Các bước: (1) sinh số bí mật; (2) biến attempts đếm lượt đoán; (3) hàm checkGuess(guess) tăng attempts, so sánh bằng if / else if / else và trả về lời nhắn; (4) gọi hàm với ba số rồi số đúng.
Giáo trình sinh số bí mật bằng Math.floor(Math.random() * 100) + 1. Vì kết quả ngẫu nhiên mỗi lần chạy khác nhau, ví dụ chạy thử dưới đây cố định số bí mật là 37 để kết quả tất định; khi chạy thật bạn thay lại bằng dòng dùng Math.random():
// const targetNumber = Math.floor(Math.random() * 100) + 1;
const targetNumber = 37; // cố định để dễ kiểm tra
let attempts = 0;
function checkGuess(guess) {
attempts++;
if (guess === targetNumber) {
return `Chúc mừng! Bạn đã đoán đúng số ${targetNumber} sau ${attempts} lần.`;
} else if (guess < targetNumber) {
return "Số bạn đoán nhỏ hơn số cần tìm.";
} else {
return "Số bạn đoán lớn hơn số cần tìm.";
}
}
console.log(checkGuess(50));
console.log(checkGuess(20));
console.log(checkGuess(30));
console.log(checkGuess(targetNumber));Bài 5.3. Thông tin sinh viên
Mục tiêu: hiển thị thông tin sinh viên trong đó sinh viên là một đối tượng. Các bước: (1) khai báo class Student với constructor(name, age, major); (2) thêm phương thức introduce() trả về chuỗi giới thiệu; (3) tạo hai đối tượng và in kết quả ra cửa sổ Log (console).
class Student {
constructor(name, age, major) {
this.name = name;
this.age = age;
this.major = major;
}
introduce() {
return `Xin chào, tôi là ${this.name}, ${this.age} tuổi, chuyên ngành ${this.major}.`;
}
}
let student1 = new Student("John", 20, "Computer Science");
let student2 = new Student("Alice", 22, "Mathematics");
console.log(student1.introduce());
console.log(student2.introduce());Chạy ứng dụng và kiểm tra kết quả in ra tại cửa sổ Log (bảng console bên dưới ví dụ, hoặc DevTools của trình duyệt khi chạy trong trang web thật).
Câu hỏi ôn tập
Giáo trình: Chương 5 — mục "Câu hỏi ôn tập lý thuyết". Các câu 1, 2 (hàm), 5, 6, 7, 8 (biến, toán tử, chuỗi) thuộc nội dung buổi 10; dưới đây là các câu 3, 4, 9, 10 của buổi này, kèm vài câu bổ sung về vòng lặp, xử lý lỗi và đối tượng.
Câu 3. Làm thế nào để viết một câu lệnh if?
A. if (i == 5) B. if i == 5 then C. if i = 5 then D. if i = 5
Đáp án: A. Điều kiện đặt trong ngoặc tròn và dùng toán tử so sánh == (hoặc ===); = là phép gán.
Câu 4. Làm thế nào để viết câu điều kiện thực thi mã lệnh nếu i KHÔNG bằng 5?
A. if i =! 5 then B. if i <> 5 C. if (i != 5) D. if (i <> 5)
Đáp án: C. Toán tử "khác" trong JavaScript là != (hoặc !== so sánh chặt); <> và =! không hợp lệ.
Câu 9. Cú pháp nào được dùng để tạo một mảng?
A. array() B. list() C. [] D. set()
Đáp án: C. Mảng khai báo bằng dấu ngoặc vuông [] (hoặc new Array(...)).
Câu 10. Cú pháp nào được dùng để truy xuất một phần tử trong mảng?
A. get() B. set() C. arr[i] D. index()
Đáp án: C. Dùng ngoặc vuông với chỉ số, chỉ số đầu tiên là 0.
Câu bổ sung 11. Điều gì xảy ra nếu bỏ break ở một case trong switch?
Đáp án: Khối lệnh của case kế tiếp vẫn được thực thi (fall-through) dù giá trị không khớp, cho tới khi gặp break hoặc hết switch.
Câu bổ sung 12. while và do-while khác nhau ở điểm nào?
Đáp án: while kiểm tra điều kiện trước nên có thể không chạy lần nào; do-while chạy khối lệnh trước rồi mới kiểm tra nên luôn chạy ít nhất một lần.
Câu bổ sung 13. Khi nào dùng for/in, khi nào dùng for/of?
Đáp án: for/in lặp qua tên thuộc tính của đối tượng; for/of lặp qua giá trị của các cấu trúc iterable như mảng, chuỗi, Map, Set.
Câu bổ sung 14. Khối finally có chạy khi try không có lỗi không?
Đáp án: Có. finally luôn được thực hiện, dù try có phát sinh lỗi hay không.
Câu bổ sung 15. Vì sao nên throw new Error("...") thay vì throw "..."?
Đáp án: Đối tượng Error giữ được tên lỗi (name), thông báo (message) và vết gọi hàm (stack), thuận tiện cho việc gỡ lỗi.
Câu bổ sung 16. Phương thức static gọi như thế nào?
Đáp án: Gọi qua lớp (Car.hello()), không gọi qua đối tượng (myCar.hello() gây lỗi).
Bài tập tự thực hành
Giáo trình: Chương 5 — mục "Bài tập tự thực hành" (Bài 5.4. Tính toán diện tích hình chữ nhật; Bài 5.5. Đếm số lần xuất hiện của các từ trong văn bản).
Bài 5.4. Tính toán diện tích hình chữ nhật
Yêu cầu: chương trình yêu cầu người dùng nhập chiều dài và chiều rộng, tính và hiển thị diện tích; xử lý các trường hợp lỗi nhập liệu (không phải số, giá trị âm). Gợi ý lời giải: viết hàm nhapSo dùng prompt, Number(), kiểm tra isNaN và <= 0, ném Error khi sai; bọc phần chính trong try...catch. Ví dụ chạy thử dưới đây dùng ba lượt nhập có sẵn (// @prompt): hợp lệ, không phải số, số âm.
function nhapSo(ten) {
const s = prompt("Nhập " + ten + ":");
const v = Number(s);
if (s === null || s.trim() === "" || isNaN(v)) {
throw new Error(ten + " phải là một số");
}
if (v <= 0) {
throw new Error(ten + " phải lớn hơn 0");
}
return v;
}
for (let lan = 1; lan <= 3; lan++) {
try {
const dai = nhapSo("chiều dài");
const rong = nhapSo("chiều rộng");
console.log("Lượt " + lan + ": diện tích = " + dai * rong);
} catch (err) {
console.log("Lượt " + lan + ": lỗi nhập liệu - " + err.message);
}
}Bài 5.5. Đếm số lần xuất hiện của các từ trong văn bản
Yêu cầu: nhập một đoạn văn bản; đếm số lần xuất hiện của mỗi từ; hiển thị danh sách từ và số lần; bỏ qua khác biệt chữ hoa/thường ("Hello" và "hello" là một từ). Gợi ý: toLowerCase(), split(/\s+/), một đối tượng (hoặc Map) làm bộ đếm, duyệt bằng for/of rồi in bằng for/in.
const van = prompt("Nhập một đoạn văn bản:");
const tu = van.toLowerCase().split(/\s+/).filter((w) => w !== "");
const dem = {};
for (const w of tu) {
dem[w] = (dem[w] || 0) + 1;
}
for (const w in dem) {
console.log(w + ": " + dem[w]);
}Sai lầm thường gặp
| Sai lầm | Hậu quả | Cách đúng |
|---|---|---|
Nhớ nhầm chỉ số mảng bắt đầu từ 1, truy cập a[a.length] |
Nhận undefined hoặc lệch một phần tử |
Chỉ số từ 0 đến length - 1; phần tử cuối là a[a.length - 1] |
Dùng delete a[i] để xóa phần tử mảng |
Tạo "lỗ hổng", độ dài mảng không đổi | Dùng splice(i, 1), pop() hoặc shift() |
sort() mảng số mà không có hàm so sánh |
[10, 9, 100, 1] thành [1, 10, 100, 9] |
sort((a, b) => a - b) |
Viết if (x = 5) (dấu gán) |
Luôn gán x và điều kiện luôn đúng |
Dùng === (hoặc ==) để so sánh |
Quên break trong switch |
Các case sau cũng chạy (fall-through) |
Thêm break, hoặc chủ ý gom case liên tiếp |
Quên cập nhật biến đếm trong while |
Vòng lặp vô hạn, treo trình duyệt | Đảm bảo có lệnh làm điều kiện thành sai (ví dụ i++) |
Dùng for/in để duyệt mảng lấy giá trị |
Nhận chỉ số dạng chuỗi, không phải giá trị | Dùng for/of hoặc forEach cho mảng; for/in cho đối tượng |
throw "lỗi" (ném chuỗi) |
Mất name, message và vết gọi hàm |
throw new Error("lỗi") |
Đặt lệnh dọn dẹp sau try...catch thay vì trong finally |
Lệnh bị bỏ qua nếu catch lại ném lỗi |
Đặt vào khối finally |
Gọi phương thức static qua đối tượng |
TypeError: ... is not a function |
Gọi qua lớp: Car.hello() |
Dùng class trước dòng khai báo |
ReferenceError (vùng chết tạm thời) |
Khai báo lớp trước khi new |
let b = a rồi sửa b, tưởng a không đổi |
Đối tượng/mảng là tham chiếu, a đổi theo |
Sao chép: { ...a }, [...a], structuredClone(a) |
Tóm tắt
- Mảng: lưu nhiều giá trị, chỉ số từ 0; thêm/xóa bằng
push,pop,shift,unshift,splice; ghép/cắt bằngconcat,slice; duyệt bằngfor,forEach,map,filter,reduce,every; sắp xếp bằngsort(dùng hàm so sánh với số/đối tượng) vàreverse. - Câu điều kiện:
if / else if / elsecho khoảng giá trị;switchcho giá trị cụ thể, nhớbreak,default, gomcase. - Vòng lặp:
while(kiểm tra trước),do-while(chạy ít nhất một lần),for(biết trước số lần),for/in(tên thuộc tính),for/of(giá trị iterable);breakthoát,continuebỏ lượt, nhãn để thoát vòng lồng. - Xử lý lỗi:
trychạy thử,catch (err)xử lý vớierr.namevàerr.message,throw new Error(...)chủ động ném lỗi,finallyluôn chạy. - Đối tượng: tạo bằng
{}, hàm khởi tạo +prototype,class; kế thừa bằngObject.create,call(),extends/super;staticgọi qua lớp;get/setdùng như thuộc tính. - Đối tượng và mảng là kiểu tham chiếu: gán sang biến khác chỉ chia sẻ cùng một đối tượng.