Buổi 10: JavaScript căn bản (1): giới thiệu, biến, kiểu dữ liệu, toán tử, hàm
Hãy hình dung bạn vừa dựng xong trang cửa hàng sách bằng HTML và CSS: bố cục đẹp, màu sắc hài hòa. Nhưng khi khách bấm "Thêm vào giỏ" thì không có gì xảy ra, ô nhập số lượng không kiểm tra giá trị, tổng tiền không tự cập nhật. Đó là lúc cần đến JavaScript — ngôn ngữ lập trình đem lại "hành vi" cho trang Web. Buổi học hôm nay là bước vào cánh cửa đó: bạn sẽ làm quen với ngôn ngữ, cách chèn mã vào trang, rồi học bốn khối kiến thức nền tảng của mọi chương trình — biến, kiểu dữ liệu, toán tử và hàm. Mọi ví dụ trong bài đều có nút "▶ Chạy thử" để bạn tự kiểm chứng ngay trên trình duyệt.
Mục tiêu buổi học
- Sau buổi này bạn có thể giải thích vai trò của JavaScript bên cạnh HTML và CSS, chèn mã vào trang bằng thẻ
<script>(nhúng trực tiếp hoặc tệp.js), và xem kết quả bằngconsole.log(). - Sau buổi này bạn có thể khai báo biến bằng
var,let,const, đặt tên đúng quy tắc, và giải thích được phạm vi biến (cục bộ, toàn cục, khối lệnh) cũng như hiện tượng nâng khai báo (hoisting). - Sau buổi này bạn có thể phân biệt các kiểu dữ liệu số, chuỗi, đúng sai, đối tượng,
null,undefined; dùngtypeof,instanceofđể kiểm tra và các hàmNumber(),String(),Boolean(),parseInt(),parseFloat()để chuyển kiểu. - Sau buổi này bạn có thể sử dụng đúng các toán tử gán, toán học, so sánh (
==khác===), logic (&&,||,!) và toán tử bit. - Sau buổi này bạn có thể khai báo và gọi hàm (hàm truyền thống, hàm mũi tên), hiểu
return, biến cục bộ trong hàm, và dùng các hàm có sẵn củaMath,Array.
Bản đồ buổi học
| Phần | Nội dung | Thời lượng gợi ý |
|---|---|---|
| 1. Giới thiệu về JavaScript | Khái niệm, chèn mã vào HTML, hiển thị log, chú thích, từ khóa, Node.js | 25 phút |
| 2. Sử dụng biến | Khai báo biến, phạm vi biến, hoisting — gồm ~20 phút làm bài nhóm 10.1 | 35 phút |
| 3. Kiểu dữ liệu | Số, chuỗi, đúng sai, đối tượng, rỗng, kiểm tra kiểu, chuyển kiểu — gồm ~20 phút làm bài nhóm 10.2 | 40 phút |
| 4. Toán tử | Gán, toán học, so sánh, logic, kiểm tra kiểu, bit — gồm ~20 phút làm bài nhóm 10.3 | 35 phút |
| 5. Hàm | Khai báo hàm, hàm mũi tên, hàm có sẵn — gồm ~15 phút làm bài nhóm 10.4 | 30 phút |
| Thực hành, ôn tập, tổng kết | Bài 5.4 của giáo trình, câu hỏi ôn tập, tóm tắt | 15 phút |
Tổng cộng 180 phút, trong đó thực hành (chạy thử ví dụ, tự gõ mã, làm bài tập trên hệ thống chấm) chiếm khoảng một nửa.
1. Giới thiệu về JavaScript
Giáo trình: Chương 5 — mục "Giới thiệu về JavaScript" (Khái niệm JavaScript; Chèn JavaScript vào tài liệu HTML; Hiển thị Log; Ghi chú thích; Từ khóa, từ dành riêng; Thực thi mã JavaScript bằng Node.js).
1.1. Khái niệm JavaScript
JavaScript là ngôn ngữ lập trình được sử dụng rộng rãi ở hai phía:
- Phía trang Web (phía người dùng — chạy ngay trong trình duyệt);
- Phía máy chủ (với Node.js).
JavaScript là một trong "3 ngôn ngữ" mà mọi nhà phát triển Web đều phải biết: (1) HTML định nghĩa nội dung của trang Web, (2) CSS xác định cách thức nội dung được hiển thị, và (3) JavaScript lập trình các hành vi của các phần tử trên trang. Ngày nay JavaScript còn được dùng cho cả ứng dụng máy tính, máy chủ và thiết bị di động.
<h4>Khóa học HTML</h4><p>Học dựng trang Web…</p><button>Đăng ký</button>
Học dựng trang Web từ đầu.
.card { border-radius: 12px; }h4 { color: #4f46e5; }button { background: #4f46e5; }
Khóa học HTML
Học dựng trang Web từ đầu.
const btn = document.querySelector('button')btn.onclick = () => {btn.textContent = 'Đã đăng ký ✓'}
Khóa học HTML
Học dựng trang Web từ đầu.
HTML cho biết trang có gì, CSS quyết định trang trông thế nào, JavaScript quyết định trang làm gì khi người dùng thao tác.
Hình trên cho thấy cách chia vai: HTML là "bộ khung", CSS là "lớp sơn", JavaScript là "động cơ". Khi bạn đổi nội dung một phần tử, đổi màu khi rê chuột, hay kiểm tra dữ liệu trước khi gửi biểu mẫu, đó đều là việc của JavaScript.
JavaScript có thể thay đổi nội dung phần tử HTML. Giáo trình lấy ví dụ một trang liên hệ có tiêu đề "Hòm thư" và một thẻ <a> còn trống, ẩn đi (display: none). Đoạn mã dưới đây tìm thẻ <a> theo id, điền địa chỉ thư, đổi thuộc tính href và cho phần tử hiện ra. Hãy bấm chạy và quan sát bản xem trước:
<h4 class="contact-title">Hòm thư</h4>
<a href="mailto:[email protected]" id="info-email" style="display: none"></a>
<script>
let emailElement = document.getElementById("info-email");
emailElement.innerText = "[email protected]";
emailElement.href = "mailto:[email protected]";
emailElement.style.display = "block";
console.log("href mới:", emailElement.href);
</script>Một trong những phương thức thông dụng của đối tượng document là getElementById(): JavaScript tìm phần tử theo id trong tài liệu HTML, từ đó ta đọc và thay đổi được nội dung phần tử. Ở ví dụ trên, phần tử <a> có id "info-email" được tìm thấy; ta đổi nội dung hiển thị (innerText), thuộc tính href và cả định dạng (style.display). Nghĩa là JavaScript đổi được nội dung, thuộc tính và định dạng (style) của phần tử HTML.
Lưu ý: Bài hôm nay tập trung vào ngôn ngữ JavaScript (biến, kiểu, toán tử, hàm). Việc thao tác sâu với cây DOM và sự kiện là nội dung của buổi 12. Hiện giờ bạn chỉ cần hiểu:
document.getElementById("...")là cách "với tay" tới một phần tử trên trang.
1.2. Chèn JavaScript vào tài liệu HTML
Để chèn mã JavaScript vào HTML ta dùng thẻ <script>. Phần tử này khai báo một kịch bản JavaScript bằng cách viết mã trực tiếp bên trong nó (đây chính là cách ta vừa dùng ở ví dụ trước):
<script>
let emailElement = document.getElementById("info-email")
emailElement.innerText = "[email protected]";
emailElement.href = "mailto:[email protected]"
emailElement.style.display = "block"
</script>Các thẻ <script> có thể đặt ở bất kỳ vị trí nào trong tệp HTML. Thông thường, mã được lưu trong tệp riêng có phần mở rộng .js; khi đó ta tham chiếu qua thuộc tính src của thẻ <script>:
<script src="js/main.js"></script><buttononclick="alert('Chào!')">Bấm</button>
- ✓ Ngắn, tiện thử nhanh
- ✗ Trộn HTML với JS, khó bảo trì
- ✗ Không dùng lại được
<script>let e = document.getElementById("info")e.innerText = "Xin chào"</script>
- ✓ Dễ thấy, không cần tệp thêm
- ✓ Đặt ở bất kỳ vị trí nào
- ✗ Khó dùng lại giữa nhiều trang
<!-- index.html --><script src="js/main.js"></script>// js/main.jsconsole.log("OK")
- ✓ Tách HTML và JS, dễ đọc, dễ bảo trì
- ✓ Trình duyệt lưu đệm → tải nhanh hơn
- ✓ Dùng chung cho nhiều trang
<noscript>Không hỗ trợ JavaScript</noscript><script> cuối <body>DOM đã dựng xong khi mã chạy; cách làm quen thuộc, an toàn.<script src="a.js">Mặc định (đồng bộ): chặn việc phân tích HTML cho tới khi tải và chạy xong.<script src="a.js" defer>Tải song song, chạy sau khi phân tích HTML xong và đúng thứ tự khai báo.<script src="a.js" async>Tải song song, tải xong là chạy ngay (chen ngang), thứ tự không đảm bảo.Mã có thể nằm trong thuộc tính thẻ (inline), trong thẻ <script> (nội bộ) hoặc tệp .js riêng (ngoài); vị trí thẻ và defer/async quyết định thời điểm chạy.
Hình trên tóm tắt các cách nhúng mã: nhúng trực tiếp trong thẻ <script>, nhúng bằng tệp ngoài qua src, và (ở buổi sau) gắn vào thuộc tính sự kiện của phần tử. Giáo trình nêu rõ lợi thế của việc dùng tệp .js riêng:
- Tách mã JavaScript khỏi mã HTML, giúp mã dễ đọc và dễ bảo trì;
- Tệp JavaScript thường được lưu trong bộ đệm (cache) của trình duyệt, nên các lần tải trang sau nhanh hơn.
Tuy nhiên, người dùng có thể tắt JavaScript trên trình duyệt. Khi đó trang có thể hiển thị không đúng ý đồ. Để dự phòng ta dùng thêm phần tử <noscript>: nội dung của nó chỉ hiển thị khi trình duyệt không chạy được kịch bản.
<script>
let emailElement = document.getElementById("info-email")
emailElement.innerText = "[email protected]";
</script>
<noscript>Không hỗ trợ JavaScript</noscript>Mẹo: Khi làm bài thật, hãy đặt thẻ
<script src="...">ở cuối<body>(hoặc thêm thuộc tínhdefer) để mã chạy sau khi các phần tử đã được tạo ra. Nếu đặt ở<head>mà không códefer, lệnhgetElementByIdcó thể không tìm thấy phần tử vì trang chưa tải xong.
1.3. Hiển thị Log
Trong quá trình lập trình, muốn biết giá trị của một biến hay biểu thức tại một thời điểm, ta dùng phương thức console.log(); giá trị cần in sẽ hiện trong cửa sổ log. Ví dụ in ra biến month:
const date = new Date('December 25, 2023');
const month = date.getMonth();
console.log("Month = " + month);Kết quả là Month = 11: phương thức getMonth() trả về giá trị từ 0 đến 11, ứng với tháng 1 đến tháng 12. Trong trình duyệt Chrome, bấm F12 để mở Developer Tools rồi chuyển sang tab Console để xem kết quả của console.log. Trên trang học này, bảng "Kết quả" bên dưới nút chạy thử chính là cửa sổ Console thu nhỏ.
Ngoài console.log(), nhà phát triển còn có các cách khác để xuất thông tin ra trình duyệt:
- Thay đổi nội dung phần tử HTML qua thuộc tính
innerTexthoặcinnerHTML(thường lấy phần tử bằnggetElementById(id)trước); - Phương thức
document.write()— chỉ phù hợp cho mục đích kiểm thử. Nếu gọi sau khi trang đã tải xong, toàn bộ nội dung đang có của trang sẽ bị xóa; - Phương thức
alert()— hiển thị một hộp thoại.
Ví dụ bên dưới dùng cả bốn cách trong cùng một trang:
<p id="a">Ô A</p>
<p id="b">Ô B</p>
<script>
console.log("1) console.log: in ra Console");
document.getElementById("a").innerText = "2) innerText: <b>không</b> in đậm";
document.getElementById("b").innerHTML = "3) innerHTML: <b>có</b> in đậm";
document.write("<p>4) document.write: chỉ nên dùng khi kiểm thử</p>");
alert("5) alert: hộp thoại");
</script>Hãy để ý sự khác nhau giữa innerText (coi mọi thứ là văn bản thuần, hiện cả dấu <b>) và innerHTML (phân tích chuỗi thành thẻ HTML). Cách phổ biến và dễ dàng nhất để kiểm tra mã vẫn là console.log().
Cảnh báo: Không bao giờ gán dữ liệu người dùng nhập vào
innerHTMLmà chưa xử lý — kẻ xấu có thể chèn mã độc (tấn công XSS). Với văn bản thông thường hãy dùnginnerText/textContent.
1.4. Ghi chú thích
Để viết chú thích trong mã JavaScript ta dùng // (chú thích một dòng) hoặc /* ... */ (chú thích nhiều dòng). Ngoài việc ghi chú, hai ký hiệu này còn dùng để tạm ngăn một khối lệnh thực thi khi bạn đang gỡ lỗi.
const date = new Date('December 25, 2023');
const month = date.getMonth(); // Lấy ra tháng của ngày Giáng sinh
console.log("Month = " + month); // Hiển thị ra cửa sổ Console
// console.log("Dòng này bị tạm tắt nên không in ra");Chú thích nhiều dòng thường dùng để mô tả mục đích của cả một đoạn mã. Ví dụ dưới đây (từ giáo trình) đưa ra lời chúc phù hợp thời điểm — chú ý getMonth() trả về 0–11 tương ứng tháng 1–12. Để kết quả luôn giống nhau mỗi lần chạy, ta dùng một ngày cố định thay cho ngày hiện tại:
<p id="myquote"></p>
<script>
/*
Đưa ra lời chúc mừng phù hợp thời điểm
- Tháng 11: Chúc mừng ngày nhà giáo
- Tháng 12: Chúc mừng lễ giáng sinh
(Chú ý với hàm getMonth() trả về giá trị từ 0-11 tương ứng tháng 1-12)
*/
const date = new Date("2023-11-20T09:00:00"); // thử đổi thành 2023-12-24T09:00:00
const myquote = document.getElementById("myquote");
if (date.getMonth() == 10) {
myquote.innerHTML = "Mừng ngày <b>nhà giáo Việt Nam</b>";
} else if (date.getMonth() == 11) {
myquote.innerHTML = "Chúc <b>giáng sinh</b> vui vẻ";
}
console.log("Tháng (0-11):", date.getMonth());
</script>Mẹo: Hãy tập thói quen viết chú thích giải thích vì sao làm như vậy, thay vì chỉ lặp lại làm gì (mã đã tự nói điều đó). Chú thích tốt giúp chính bạn, vài tuần sau, đọc lại được mã của mình.
1.5. Từ khóa, từ dành riêng
Mỗi ngôn ngữ lập trình đều có tập "từ khóa" (keywords) hay "từ dành riêng" (reserved words) — những từ có ý nghĩa đặc biệt trong chương trình. Ta không được dùng các từ này để đặt tên cho khai báo mới như biến, phương thức, lớp, đối tượng... Một số từ khóa tiêu biểu trong Bảng 5-1 của giáo trình (các từ có dấu * được bổ sung từ phiên bản ECMAScript 6 — ES6):
| Nhóm | Ví dụ từ khóa |
|---|---|
| Khai báo | var, let*, const, function, class*, enum*, import*, export* |
| Điều khiển luồng | if, else, switch, case, default, for, while, do, break, continue, return |
| Xử lý lỗi | try, catch, finally, throw |
| Đối tượng và kiểu | new, this, typeof, instanceof, delete, in, void, null, true, false, extends*, super* |
| Khác | await*, yield, with, debugger, static, public, private, package... |
Trong JavaScript ta không thể dùng các từ dành riêng làm tên biến, nhãn hoặc tên hàm. Ngoài ra nên tránh đặt trùng với:
- tên đối tượng, thuộc tính, phương thức tích hợp sẵn như
Array,Date,Math,valueOf()...; - tên đối tượng và thuộc tính HTML/Window như
button,confirm,history...; - tên của các trình xử lý sự kiện HTML như
onblur,onclick,onmouseover,onkeydown...
Thử xem điều gì xảy ra khi dùng từ khóa làm tên biến:
try {
eval("var class = 'Cong nghe thong tin'");
} catch (e) {
console.log("Lỗi:", e.name); // SyntaxError
}
var lop = "Cong nghe thong tin"; // đặt tên khác thì hợp lệ
console.log(lop);1.6. Thực thi mã JavaScript bằng Node.js
JavaScript không chỉ chạy trong trình duyệt. Node.js là môi trường cho phép chạy mã JavaScript trên máy tính (thay vì trong trình duyệt), dùng để phát triển ứng dụng phía máy chủ, ứng dụng dòng lệnh và nhiều công cụ khác. Để thực thi, bạn tạo một tệp JavaScript rồi dùng lệnh node tên_file.js; Node.js sẽ tải, biên dịch và chạy mã. Ví dụ chạy các câu lệnh trong tệp index.js:
node index.jsViệc lập trình Node.js viết kịch bản phía máy chủ nằm ngoài phạm vi giáo trình này. Tuy vậy, Node.js rất tiện để bạn thử nhanh các đoạn mã thuần ngôn ngữ (biến, hàm, toán tử) mà không cần dựng trang HTML.
Mở rộng: Trong Node.js không có
windowhaydocument(vì không có trang Web); ngược lại nó có các mô-đun đọc/ghi tệp, tạo máy chủ HTTP... Phần lõi ngôn ngữ (biến, kiểu, toán tử, hàm) thì giống hệt nhau ở cả hai môi trường — nên những gì học hôm nay dùng được ở cả hai nơi.
(Chương trình không in gì.)
Chạy trong khung cách ly ngay trên trình duyệt của bạn; không có mạng.
Trình soạn ở trên (kiểu CodePen) cho phép bạn gõ HTML/CSS/JS rồi xem kết quả ngay — hãy dùng nó để thử mọi ví dụ của buổi học này.
2. Sử dụng biến
Giáo trình: Chương 5 — mục "Sử dụng biến" (Khai báo biến; Phạm vi biến; Nâng khai báo (Hoisting)).
2.1. Khai báo biến
Trong mọi ngôn ngữ lập trình, biến dùng để lưu trữ các giá trị dữ liệu. JavaScript có ba từ khóa khai báo biến:
var— cách viết cũ;letvàconst— có từ ES6, được khuyến nghị dùng cho mã mới.
var a = 21;
var b = -6;
var c = a + b;
console.log(a, b, c); // 21 -6 15Khi khai báo biến trong JavaScript, kiểu dữ liệu là động (dynamic typing): một biến có thể lưu các kiểu dữ liệu khác nhau tại các thời điểm khác nhau — lúc là số, lúc là chuỗi ký tự.
let x = 100; // x đang là số
console.log(typeof x, x);
x = "một trăm"; // cùng biến x, giờ là chuỗi
console.log(typeof x, x);
x = true; // và giờ là đúng/sai
console.log(typeof x, x);Quy tắc đặt tên biến (giáo trình):
- Không đặt tên trùng với từ khóa, từ dành riêng đã nêu ở mục 1.5.
- Tên biến có thể chứa số, chữ cái, gạch dưới (
_) hoặc ký tự$. - Ký tự đầu tiên phải là chữ cái, gạch dưới (
_) hoặc ký tự$. - JavaScript phân biệt chữ hoa và chữ thường.
Những biến sau đặt tên hợp lệ:
var ten = "Phạm Dương Minh";
var nam_sinh = 2000;
var $tuoi = 18;
var QUOCTICH1 = "Vietnam";
console.log(ten, nam_sinh, $tuoi, QUOCTICH1);Các khai báo sau dùng tên không hợp lệ (mỗi dòng đều gây lỗi cú pháp, nên đây chỉ là mã minh họa, không chạy):
var email@ = "[email protected]" // Có chứa ký tự đặc biệt
var 3diem = 8 // Bắt đầu bởi chữ số
var noi sinh = "Ha noi" // Tên biến có chứa khoảng trắng
var class = "Cong nghe thong tin" // Trùng với từ khóa classChú ý: vì JavaScript phân biệt chữ hoa, chữ thường nên name và Name là hai biến khác nhau:
let diem = 8;
let Diem = 10;
console.log(diem, Diem, diem === Diem); // 8 10 falseSau khi biến được khai báo, ta gán giá trị bằng toán tử gán (=). Việc gán có thể thực hiện ngay khi khai báo hoặc sau đó. Một điểm khác biệt quan trọng giữa các từ khóa nằm ở việc khai báo lại: nếu dùng var khai báo lại một biến đã có giá trị thì biến không bị mất giá trị; ngược lại, khai báo lại biến đã có bằng let hoặc const trong cùng một phạm vi sẽ gây lỗi cú pháp (SyntaxError).
var ho_ten = "Phạm Dương Minh";
var ho_ten; // khai báo lại, không gán
console.log(ho_ten); // vẫn là "Phạm Dương Minh"
try {
eval("let lop = 'A'; let lop = 'B';"); // let khai báo lại trong cùng phạm vi
} catch (e) {
console.log("let khai báo lại ->", e.name); // SyntaxError
}Lưu ý: Dù
varcho phép khai báo lại, đó chính là nguồn gốc của nhiều lỗi khó thấy (vô tình ghi đè biến cùng tên). Vì vậylet/constbáo lỗi để bạn phát hiện sớm. Quy tắc thực dụng: mặc định dùngconst; chỉ dùngletkhi biến cần gán lại; tránhvar.
2.2. Phạm vi biến
Phạm vi (Scope) xác định khả năng truy cập (khả năng hiển thị) của các biến. Giáo trình chia thành hai loại phạm vi: cục bộ (local) và toàn cục (global).
| var | let | const | |
|---|---|---|---|
| Phạm vi | hàm | khối { } | khối { } |
| Khai báo lại | ✓ được | ✗ lỗi | ✗ lỗi |
| Gán lại | ✓ được | ✓ được | ✗ không |
| Dùng trước dòng khai báo | undefined | ReferenceError | ReferenceError |
console.log(x) // undefinedvar x = 5
var x // khai báo nâng lên đầuconsole.log(x)x = 5 // phép gán ở lại chỗ cũ
console.log(y) // ReferenceError// ← vùng chết tạm thờilet y = 5console.log(y) // 5
Biến chỉ nhìn thấy trong phạm vi chứa nó; JavaScript tìm tên biến từ phạm vi trong cùng ra ngoài. var chỉ biết phạm vi hàm, let và const biết cả phạm vi khối.
Hình trên minh họa các "vòng bao" của phạm vi: toàn cục ở ngoài cùng, trong đó là phạm vi từng hàm, và trong hàm còn có thể có phạm vi từng khối lệnh { } (với let/const). Quy tắc chung: mã ở vòng trong nhìn thấy biến của vòng ngoài, nhưng không có chiều ngược lại.
Biến cục bộ là biến khai báo bên trong một hàm và chỉ có phạm vi trong hàm đó, nghĩa là chỉ truy cập được từ bên trong hàm. Biến cục bộ được tạo khi hàm bắt đầu và bị xóa khi hàm hoàn thành. Vì chỉ được nhận dạng bên trong hàm của chúng, các biến cùng tên có thể dùng trong các hàm khác nhau.
function myInfo() {
var ten = "Phạm Dương Minh"; // biến cục bộ của hàm myInfo()
console.log("Trong hàm:", ten);
}
myInfo();
console.log("Ngoài hàm:", typeof ten); // "undefined": không thể truy cập ten ở đây
try {
console.log(ten);
} catch (e) {
console.log(e.name + ": " + e.message);
}Biến khai báo bên ngoài mọi hàm trở thành biến toàn cục với phạm vi toàn cục — mọi phương thức đều truy cập được:
var school_name = "Kinh te quoc dan"; // biến toàn cục
console.log("Ngoài hàm:", school_name);
function myInfo() {
console.log("Trong hàm cũng thấy:", school_name);
}
myInfo();Cần đặc biệt lưu ý: nếu trong một hàm ta gán giá trị cho một biến chưa được khai báo thì ở chế độ không nghiêm ngặt (non-strict mode), biến đó tự động trở thành biến toàn cục:
myInfo();
// sau khi gọi hàm myInfo() ta mới có thể dùng biến school_name
console.log("school_name =", school_name);
function myInfo() {
school_name = "Kinh te quoc dan"; // quên khai báo => rò rỉ ra toàn cục
}Cảnh báo: Biến toàn cục "ngầm" như trên là lỗi rất khó tìm. Luôn khai báo bằng
let/const(hoặcvar) trước khi gán. Có thể bật chế độ nghiêm ngặt bằng dòng"use strict";ở đầu tệp để trình duyệt báo lỗi thay vì âm thầm tạo biến toàn cục.
let và var khác nhau về phạm vi truy cập. Với var, biến khai báo trong khối lệnh { } vẫn truy cập được ở bên ngoài khối — dễ gây nhập nhằng. Với let, biến chỉ có phạm vi trong khối lệnh đó và tự bị hủy khi khối kết thúc:
{
var a = 2;
}
console.log("a ngoài khối:", a); // 2: truy cập được
{
let b = 2;
}
console.log("b ngoài khối:", typeof b); // "undefined": không truy cập đượcXét thêm ví dụ vòng lặp for của giáo trình. Sau khi vòng lặp kết thúc, a có giá trị 10, còn b vẫn là 0. Lý do: với var, biến a khai báo trong vòng lặp chính là biến a đã khai báo bên ngoài; còn biến i khai báo bằng let chỉ tồn tại bên trong vòng for và bị hủy khi vòng lặp kết thúc nên b bên ngoài không bị ảnh hưởng.
var a = 0;
for (var a = 0; a < 10; a++) {
// some statements
}
console.log("a =", a); // 10
let b = 0;
for (let i = 0; i < 10; i++) {
// some statements
}
console.log("b =", b); // 0 (biến i chỉ sống trong vòng for)Cuối cùng, từ khóa const dùng để khai báo hằng số: bắt buộc gán giá trị ngay khi khai báo và không thể gán lại sau đó.
const PI = 3.141592653589793;
console.log("PI =", PI);
try {
PI = 3.141592; // không hợp lệ vì không thể thay đổi giá trị của PI
} catch (e) {
console.log(e.name + ": " + e.message); // TypeError
}Lưu ý quan trọng từ giáo trình: const chỉ khóa tham chiếu (khóa việc gán lại tên biến). Nếu giá trị là đối tượng hoặc mảng thì các thuộc tính, phần tử bên trong vẫn có thể thay đổi:
const sv = { ten: "Minh", tuoi: 21 };
sv.tuoi = 22; // được phép: đổi thuộc tính bên trong
console.log(sv);
const ds = [1, 2];
ds.push(3); // được phép: thêm phần tử
console.log(ds);
try {
sv = { ten: "An" }; // không được: gán lại tên biến
} catch (e) {
console.log("Gán lại sv ->", e.name);
}Bảng so sánh ba từ khóa (tổng hợp từ các mục trên):
| Tiêu chí | var |
let |
const |
|---|---|---|---|
| Phạm vi | Hàm (hoặc toàn cục) | Khối lệnh { } |
Khối lệnh { } |
| Khai báo lại cùng phạm vi | Được, giữ nguyên giá trị | Lỗi SyntaxError |
Lỗi SyntaxError |
| Gán lại giá trị | Được | Được | Không (TypeError) |
| Phải gán khi khai báo | Không | Không | Có |
| Dùng trước dòng khai báo | Được (giá trị undefined) |
Lỗi ReferenceError |
Lỗi ReferenceError |
| Khuyến nghị | Tránh dùng trong mã mới | Khi cần gán lại | Mặc định |
Mở rộng: Hãy tưởng tượng một ô nhớ có gắn nhãn tên biến.
constkhóa chặt nhãn dính vào ô nào, nhưng nếu ô đó chứa cả một "ngăn kéo" (đối tượng, mảng) thì đồ bên trong ngăn kéo vẫn thay đổi được. Buổi 11 sẽ nói kỹ hơn về kiểu tham chiếu của đối tượng và mảng.
2.3. Nâng khai báo (Hoisting)
Nâng khai báo (Hoisting) là hành vi mặc định của JavaScript: các khai báo được đưa lên đầu phạm vi chứa chúng. Khác với nhiều ngôn ngữ, trong JavaScript một biến có thể được khai báo sau khi nó được sử dụng — hợp lệ vì JavaScript di chuyển các khai báo lên trên cùng phạm vi của biến (như lên đầu hàm với biến cục bộ). Tuy nhiên, chỉ phần khai báo được nâng lên, còn phép gán vẫn nằm nguyên chỗ cũ.
Ví dụ của giáo trình hoàn toàn hợp lệ:
tuoi = 18;
ten = "Phạm Dương Minh";
var tuoi;
var ten;
console.log(ten, tuoi);Vì chỉ phần khai báo được nâng còn phần gán ở nguyên chỗ, nên nếu đọc biến trước dòng gán, ta nhận undefined:
console.log("Trước khi gán:", sinhvien); // undefined (không phải lỗi!)
var sinhvien = "Phạm Dương Minh";
console.log("Sau khi gán:", sinhvien);
// Trình duyệt hiểu như sau:
// var sinhvien; <- khai báo được nâng lên đầu
// console.log(sinhvien); -> undefined
// sinhvien = "Phạm Dương Minh"; <- phép gán ở nguyên chỗNgoài ra, việc dùng biến trước dòng khai báo chỉ hợp lệ với var (khi đó biến có giá trị undefined). Biến khai báo bằng let và const tuy cũng được nâng nhưng nằm trong "vùng chết tạm thời" (Temporal Dead Zone — TDZ): truy cập trước dòng khai báo sẽ gây lỗi ReferenceError.
try {
console.log(diemtb); // chạm vào vùng chết tạm thời
let diemtb = 8.5;
} catch (e) {
console.log(e.name); // ReferenceError
}Nâng khai báo (hoisting) và vùng chết tạm thời
chọn kịch bản, bấm Bước tiếp để chạy từng bướcMô phỏng trên cho bạn chọn var, let hoặc const rồi xem từng dòng được "nâng" thế nào và dòng nào gây lỗi. Hãy dự đoán kết quả trước khi bấm chạy.
Mẹo: Cách phòng tránh mọi rắc rối của hoisting rất đơn giản: khai báo biến ở đầu phạm vi, dùng
let/const, và không dùng biến trước khi gán giá trị.
Vừa học xong cách khai báo biến, phạm vi và hoisting, hãy làm các bài của nhóm 10.1: bắt đầu bằng bài khai báo let/const quen thuộc, rồi thử sức với các bài về phạm vi khối và "bẫy" var trong vòng lặp.
3. Kiểu dữ liệu
Giáo trình: Chương 5 — mục "Kiểu dữ liệu" (Kiểu số; Kiểu chuỗi ký tự; Kiểu đúng sai; Kiểu đối tượng; Kiểu rỗng; Kiểm tra kiểu; Chuyển kiểu).
Kiểu dữ liệu (data type) của một biến có thể là: số, chuỗi ký tự, boolean, đối tượng, kiểu rỗng (null) hoặc chưa xác định (undefined).
42 3.14 NaN"Minh" 'NEU'true falsenullundefinedSymbol("id")9007199254740993n{ ten: "Minh", tuoi: 21 }[ 10, 20, 30 ](a, b) => a + bKiểu nguyên thủy lưu trực tiếp giá trị (sao chép là tạo bản mới); kiểu đối tượng lưu tham chiếu tới cùng một đối tượng. typeof cho biết kiểu.
Hình trên chia các kiểu thành hai nhóm: kiểu nguyên thủy (số, chuỗi, boolean, null, undefined...) — mỗi biến chứa trực tiếp một giá trị; và kiểu đối tượng (object, mảng, hàm, ngày...) — biến chứa một tham chiếu tới đối tượng. Phần còn lại của mục này lần lượt đi qua từng kiểu, theo đúng thứ tự giáo trình.
3.1. Kiểu số
JavaScript chỉ có một kiểu dữ liệu số. Một số có thể có phần thập phân hoặc không, và có thể viết theo dạng mũ e:
var tuoi = 28; // Số không có phần thập phân
var hesoluong = 2.34; // Số có phần thập phân
var luongcoban = 1.5e6; // Dạng mũ e (tương đương 1500000)
var saiso = 1e-3; // Tương đương 1/1000 hay 0.001
console.log(tuoi, hesoluong, luongcoban, saiso);
console.log(typeof tuoi, typeof hesoluong); // cùng là "number"Khi số được lưu ở dạng chuỗi và cần tính toán, JavaScript tự động chuyển chuỗi thành số khi dùng các toán tử toán học trên chúng — tuy nhiên điều này chỉ đúng với nhân (*), chia (/) và trừ (-). Với toán tử cộng (+), nó trở thành phép ghép chuỗi:
var hesoluong = "2.34";
var luongcoban = "1.5e6";
console.log(luongcoban * hesoluong); // 3510000: chuỗi được ép thành số
console.log(luongcoban + hesoluong); // "1.5e62.34": ghép chuỗi!Trong trường hợp không thể thực hiện phép tính, kết quả là NaN (Not a Number):
var hesoluong = "2.34";
var luongcoban = "1.5a6"; // không phải số hợp lệ
console.log(luongcoban * hesoluong); // NaN
console.log(typeof NaN); // "number" (NaN vẫn thuộc kiểu số)
console.log(NaN === NaN); // false: NaN không bằng chính nó
console.log(Number.isNaN(NaN)); // true: cách đúng để kiểm tra NaNKhông giống nhiều ngôn ngữ khác, JavaScript không định nghĩa nhiều kiểu số khác nhau: mọi số đều là số thực 64 bit — 52 bit cho phần định trị, 11 bit cho phần số mũ và 1 bit cho dấu. Độ chính xác khoảng 15 chữ số. Số quá lớn sẽ được làm tròn và in dạng mũ; số vượt quá khoảng lưu trữ trở thành vô cùng (Infinity) hoặc âm vô cùng (-Infinity):
var so = 12345678901234567890123.1234567890;
console.log(so); // 1.2345678901234568e+22
var s = 3;
console.log(s / 0); // Infinity
console.log(-s / 0); // -Infinity
console.log(0.1 + 0.2); // 0.30000000000000004 (sai số do dùng số thực nhị phân)
console.log((0.1 + 0.2).toFixed(2)); // "0.30"Cảnh báo:
0.1 + 0.2không bằng0.3— đây không phải lỗi riêng của JavaScript mà là hệ quả của cách máy tính biểu diễn số thực nhị phân 64 bit. Khi tính tiền, hãy làm tròn kết quả (ví dụtoFixed(2)) hoặc tính theo đơn vị nhỏ nhất (đồng, xu) bằng số nguyên.
Số cũng có thể khai báo như một đối tượng bằng từ khóa new (ít dùng, vì kết quả có kiểu object):
var tuoi = new Number(20);Hàm Number() dùng để chuyển nhiều kiểu dữ liệu khác về số; còn parseInt() và parseFloat() chuyển chuỗi thành số nguyên (int) hoặc số thực (float) tương ứng:
console.log(Number(true)); // 1
console.log(Number(false)); // 0
console.log(Number("20")); // 20
console.log(Number(" 20")); // 20
console.log(Number(" 20 ")); // 20
console.log(Number("20.5")); // 20.5
console.log(Number("Hai muoi")); // NaN
console.log(parseInt("42px")); // 42: đọc các chữ số đầu rồi dừng
console.log(parseFloat("3.14abc")); // 3.14
console.log(parseInt("abc")); // NaNLưu ý:
Number("42px")choNaN(cả chuỗi phải là số), cònparseInt("42px")cho42(đọc đến khi gặp ký tự không phải chữ số). Chọn hàm nào tùy bạn muốn "nghiêm khắc" hay "rộng rãi" khi đọc dữ liệu người dùng nhập.
Số có các phương thức như toExponential(), toFixed(), toPrecision():
var gia = 180.25;
console.log(gia.toExponential()); // "1.8025e+2": dạng mũ
console.log(gia.toFixed()); // "180": làm tròn, không có phần thập phân
console.log(gia.toFixed(1)); // "180.3": giữ 1 chữ số thập phân
console.log(gia.toPrecision()); // "180.25": giữ nguyên
console.log(gia.toPrecision(4)); // "180.3": 4 chữ số có nghĩaLưu ý: Các chú thích kết quả trong ví dụ tương ứng của bản giáo trình (dạng
18000000) không khớp với giá trị180.25; kết quả thực tế khi chạy trên trình duyệt là các giá trị ghi ở khối trên. Hãy luôn tin vào kết quả chạy thật thay vì chú thích trong sách.
Lớp Number có nhiều phương thức hữu ích và các hằng số quan trọng:
console.log(Number.EPSILON); // 2.220446049250313e-16: hiệu của 1 và số nhỏ nhất lớn hơn 1
console.log(Number.MIN_VALUE); // 5e-324: số dương nhỏ nhất biểu diễn được
console.log(Number.MAX_VALUE); // 1.7976931348623157e+308: số lớn nhất
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991: số nguyên "an toàn" lớn nhất
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991: số nguyên "an toàn" nhỏ nhất
console.log(Number.NEGATIVE_INFINITY); // -Infinity
console.log(Number.POSITIVE_INFINITY); // Infinity
console.log(9007199254740991 + 2); // 9007199254740992: ngoài khoảng an toàn, kết quả sai!Các số "an toàn" là các số mà hệ thống đảm bảo tính toán chính xác; kết quả nằm ngoài khoảng "an toàn" có thể không còn chính xác.
3.2. Kiểu chuỗi ký tự
Chuỗi dùng để lưu trữ và thao tác với ký tự, văn bản. Một chuỗi được đặt trong cặp dấu nháy kép " " hoặc nháy đơn ' ':
var ten = "Phạm Dương Minh";
var lop = "Công nghệ thông tin";Dấu nháy kép có thể nằm trong chuỗi khai báo bằng nháy đơn và ngược lại mà không cần ký tự thoát; hoặc dùng ký tự thoát \. Cả bốn cách sau đều hợp lệ:
var chuoi1 = "Minh cho rằng: 'Lập trình rất đơn giản'";
var chuoi2 = 'Minh cho rằng: "Lập trình rất đơn giản"';
var chuoi3 = "Minh cho rằng: \"Lập trình rất đơn giản\"";
var chuoi4 = 'Minh cho rằng: \'Lập trình rất đơn giản\'';
console.log(chuoi1);
console.log(chuoi2);
console.log(chuoi3);
console.log(chuoi4);Ký tự \ còn giúp hiển thị các ký tự đặc biệt khác:
| Chuỗi thoát | Ý nghĩa |
|---|---|
\b |
Ký tự xóa lùi (backspace) |
\f |
Ký tự sang trang (form feed) |
\n |
Xuống dòng |
\r |
Về đầu dòng |
\t |
Ký tự tab |
\\ |
Ký tự \ |
console.log("3. Lập trình\n Web"); // xuống dòng
console.log("5. Lập trình\t Web"); // tab
console.log("6. Lập trình\\ Web"); // in ra một dấu \
// JSON.stringify cho thấy rõ ký tự thoát nằm trong chuỗi:
console.log(JSON.stringify("1. Lập trình\b Web"));
console.log(JSON.stringify("2. Lập trình\f Web"));
console.log(JSON.stringify("4. Lập trình\r Web"));Độ dài chuỗi lấy qua thuộc tính length (không có dấu ngoặc). Chuỗi có thể là một đối tượng — tạo bằng new String(); khi đó phép so sánh chặt === cho kết quả false vì hai biến khác kiểu dữ liệu (một string, một object), còn == chỉ so sánh giá trị nên cho true:
var quoc_gia = "Việt Nam";
console.log(quoc_gia.length); // 8
var name1 = "Minh"; // String
var name2 = new String("Minh"); // Object
console.log(typeof name1, typeof name2);
console.log(name1 === name2); // false
console.log(name1 == name2); // trueJavaScript cung cấp rất nhiều phương thức làm việc với chuỗi. Để tìm một chuỗi trong chuỗi khác dùng indexOf() (trả về vị trí đầu tiên, tính từ 0, hoặc -1 nếu không tìm thấy; có thể nhận tham số thứ hai là vị trí bắt đầu tìm). Muốn tìm ngược từ cuối dùng lastIndexOf():
var khauhieu = "Lập kế hoạch - Ghi chú thích - Viết mã lệnh - Kiểm thử";
console.log(khauhieu.indexOf("-")); // 13
console.log(khauhieu.indexOf("JavaScript")); // -1: không tìm thấy
console.log(khauhieu.lastIndexOf("-")); // 44Phương thức replace() thay nội dung; lưu ý nó không thay đổi chuỗi ban đầu mà trả về chuỗi mới. concat() nối hai chuỗi. trim() xóa khoảng trắng ở đầu và cuối chuỗi:
var khauhieu = "Lập kế hoạch - Ghi chú thích - Viết mã lệnh - Kiểm thử";
var khauhieu2 = khauhieu.replace("Kiểm thử", "Vận hành");
console.log(khauhieu); // chuỗi gốc không đổi
console.log(khauhieu2); // chuỗi mới
console.log(khauhieu.concat(" - Vận hành"));
var language = " JavaScript ";
console.log("[" + language.trim() + "]"); // [JavaScript]Để chuyển chuỗi thành mảng ta dùng split(), phân tách chuỗi theo một ký hiệu; kết hợp với map() và trim() để làm sạch từng phần tử. toUpperCase() đổi sang chữ hoa, toLowerCase() đổi sang chữ thường:
var khauhieu = "Lập kế hoạch - Ghi chú thích - Viết mã lệnh - Kiểm thử";
var arr = khauhieu.split("-");
arr = arr.map(s => s.trim()); // duyệt và trim() các phần tử trong mảng
console.log(arr); // ['Lập kế hoạch','Ghi chú thích','Viết mã lệnh','Kiểm thử']
var kh = "JavaScript";
console.log(kh.toUpperCase()); // JAVASCRIPT
console.log(kh.toLowerCase()); // javascriptNgoài ra còn nhiều phương thức khác như slice(), substring(), substr(), padStart(), padEnd(), charAt()...
var s = "JavaScript";
console.log(s.slice(0, 4)); // "Java"
console.log(s.substring(4)); // "Script"
console.log(s.charAt(0)); // "J"
console.log("7".padStart(3, "0")); // "007"
console.log("ab".padEnd(5, "-")); // "ab---"Mở rộng: Chuỗi viết trong cặp dấu huyền (
`) gọi là template literal, cho phép chèn biểu thức bằng${...}và viết nhiều dòng — rất tiện khi ghép chuỗi:`Xin chào ${ten}, bạn ${tuoi} tuổi`. Bạn sẽ gặp cách viết này trong các ví dụ ở buổi sau.
3.3. Kiểu đúng sai
Biến kiểu Boolean chỉ nhận một trong hai giá trị true hoặc false; thường dùng trong các biểu thức điều kiện.
let lapgiadinh = false;
if (lapgiadinh) {
console.log("Đã lập gia đình");
} else {
console.log("Chưa lập gia đình");
}
let dubang = 5 > 3; // kết quả của biểu thức so sánh cũng là boolean
console.log(dubang, typeof dubang);Kiểu Boolean cũng có thể biểu diễn dưới dạng đối tượng qua lớp Boolean: let lapgiadinh = new Boolean(false);. Tương tự String và Number, biến khai báo bằng lớp Boolean có kiểu Object. Trong hầu hết trường hợp việc dùng đối tượng Boolean là không cần thiết: mã dài hơn, chạy chậm hơn — và còn tiềm ẩn bẫy:
let a = false;
let b = new Boolean(false);
console.log(typeof a, typeof b); // boolean object
console.log(a ? "a đúng" : "a sai"); // a sai
console.log(b ? "b đúng" : "b sai"); // b đúng! (đối tượng luôn là "truthy")Lưu ý: Khi một giá trị được đặt vào điều kiện
if, JavaScript tự chuyển nó về đúng/sai. Các giá trị chuyển thànhfalse(gọi là falsy) gồm:false,0,""(chuỗi rỗng),null,undefined,NaN. Mọi giá trị khác (kể cả"0",[],{}) là truthy. Mục 4.4 sẽ dùng nhiều đến khái niệm này.
3.4. Kiểu đối tượng
Một biến JavaScript có thể là một đối tượng. Đối tượng được đặt trong cặp ngoặc nhọn {}; các thuộc tính viết dưới dạng cặp tên thuộc tính : giá trị (phân cách bởi dấu hai chấm :), và các thuộc tính phân cách nhau bằng dấu phẩy ,. Ví dụ đối tượng mô tả một sinh viên:
var sinhvien = {
ho: "Pham",
dem: "Duong",
ten: "Minh",
tuoi: 21,
quequan: "Hai Duong",
lop: "Cong nghe thong tin 62"
};
console.log(sinhvien.ten, sinhvien.tuoi); // truy cập thuộc tính bằng dấu chấm
console.log(typeof sinhvien);Cũng có thể tạo đối tượng bằng từ khóa new, từ các lớp tự định nghĩa hoặc có sẵn. Ví dụ tạo đối tượng ngày tháng từ lớp Date (định dạng ISO: năm-tháng-ngày):
var ngaysinh = new Date("2000-12-25");
console.log(ngaysinh.toISOString()); // 2000-12-25T00:00:00.000Z
console.log(ngaysinh.getUTCFullYear()); // 2000
console.log(typeof ngaysinh); // "object"Kiểu dữ liệu đối tượng gắn liền với lập trình hướng đối tượng và sẽ được trình bày kỹ hơn ở buổi 11 (mảng, đối tượng) của giáo trình.
3.5. Kiểu rỗng
Kiểu rỗng (null) nghĩa là "không có gì": dùng để biểu thị dữ liệu rỗng hoặc không còn lưu trữ gì. Một biến chưa được gán giá trị thì có kiểu/giá trị undefined:
var sinhvien1 = { ho: "Pham", dem: "Duong", ten: "Minh", tuoi: 21 };
sinhvien1 = null;
console.log(sinhvien1); // null
console.log(typeof sinhvien1); // "object" — kiểu của null vẫn là object (một "di sản" của JavaScript)
var sinhvien2; // khai báo nhưng chưa gán
console.log(sinhvien2); // undefined
console.log(typeof sinhvien2); // "undefined"
console.log(null == undefined, null === undefined); // true falseMẹo: Hãy nhớ phân biệt:
undefinedlà "biến chưa có giá trị" (do JavaScript gán), cònnulllà "tôi cố ý để trống" (do lập trình viên gán). Muốn giải phóng đối tượng, hoặc báo hiệu "chưa có dữ liệu", ta gánnull.
3.6. Kiểm tra kiểu
Để kiểm tra kiểu của một biến tại một thời điểm, dùng toán tử typeof. Với đối tượng, muốn biết biến có phải là thể hiện (instance) của một lớp hay không thì dùng toán tử instanceof.
console.log(typeof true); // "boolean"
console.log(typeof {}); // "object"
console.log(typeof "Minh"); // "string"
console.log(typeof 21); // "number"
console.log(typeof x); // "undefined" vì x là biến chưa khai báo (không gây lỗi)
console.log(typeof null); // "object" (đặc điểm cần nhớ)
console.log(typeof [1, 2]); // "object" (mảng cũng là object)
console.log(typeof function () {}); // "function"function SinhVien(ten, tuoi) {
this.ten = ten;
this.tuoi = tuoi;
}
const sinhvien = new SinhVien('Dương Minh', 21);
console.log(typeof sinhvien); // object
console.log(sinhvien instanceof String); // false
console.log(sinhvien instanceof SinhVien); // trueLưu ý:
typeofkhông phân biệt mảng với đối tượng thường và trả"object"chonull. Muốn biết một giá trị có phải mảng, dùngArray.isArray(giatri); muốn biết có phảinull, so sánhgiatri === null.
3.7. Chuyển kiểu
Các biến JavaScript có thể được chuyển đổi sang kiểu dữ liệu khác mà vẫn "giữ" giá trị. Hàm String() chuyển một giá trị bất kỳ thành chuỗi:
console.log(String(21)); // "21"
console.log(String({ ten: "Minh", tuoi: 21 })); // "[object Object]"
console.log(String(true)); // "true"
console.log(String(null)); // "null"
console.log(String(undefined)); // "undefined"
console.log(typeof String(new Date("2000-12-25"))); // "string"Phương thức toString() cũng làm điều tương tự, nhưng chỉ gọi được từ biến đã có giá trị — không thể gọi từ null hoặc undefined:
let x = 21;
console.log(x.toString()); // "21"
x = { ten: "Minh", tuoi: 21 };
console.log(x.toString()); // "[object Object]"
x = true;
console.log(x.toString()); // "true"
x = null;
try {
console.log(x.toString()); // không thể thực hiện được
} catch (e) {
console.log(e.name); // TypeError
}Các hàm Number() và Boolean() có tác dụng tương tự String(): chuyển các kiểu khác nhau thành Number và Boolean. JavaScript còn cung cấp parseInt(), parseFloat(). Bảng tổng hợp một số phép chuyển phổ biến:
| Giá trị | Number(...) |
String(...) |
Boolean(...) |
|---|---|---|---|
"20" |
20 | "20" | true |
"" (chuỗi rỗng) |
0 | "" | false |
"Hai muoi" |
NaN | "Hai muoi" | true |
true |
1 | "true" | true |
false |
0 | "false" | false |
null |
0 | "null" | false |
undefined |
NaN | "undefined" | false |
const mau = ["20", "", "Hai muoi", true, false, null, undefined];
for (const v of mau) {
console.log(String(v).padEnd(10), Number(v), Boolean(v));
}Cần lưu ý rằng việc chuyển kiểu có thể phát sinh lỗi khi dữ liệu không thể chuyển được, vì vậy cần xử lý lỗi (ngoại lệ) để chương trình hoạt động ổn định — nội dung xử lý lỗi try...catch sẽ học ở buổi 11.
Ví dụ thực tế: Ô nhập số lượng trong giỏ hàng luôn trả về chuỗi (ví dụ
"3"), kể cả khi người dùng gõ số. Nếu bạn viếtsoluong + 1sẽ ra"31"; phải viếtNumber(soluong) + 1mới ra4. Chuyển kiểu đúng chỗ là kỹ năng cơ bản khi làm biểu mẫu.
Vừa học xong các kiểu dữ liệu, typeof và chuyển kiểu, hãy làm các bài nhóm 10.2: nhận diện kiểu của giá trị, chuyển chuỗi thành số, xử lý chuỗi, các số đặc biệt (NaN, Infinity) và bài toán sai số khi tính tiền.
4. Toán tử
Giáo trình: Chương 5 — mục "Toán tử" (Toán tử gán; Toán tử toán học; Toán tử so sánh; Toán tử logic; Toán tử kiểm tra kiểu; Toán tử nhị phân).
Toán tử (operator) là thành phần không thể thiếu của một biểu thức: nó kết hợp với các toán hạng (operand) để tạo thành biểu thức, phục vụ nhiệm vụ tính toán của chương trình. Trong biểu thức a + b, dấu + là toán tử còn a, b là toán hạng. JavaScript có nhiều loại toán tử:
- Toán tử gán;
- Toán tử toán học;
- Toán tử với chuỗi;
- Toán tử so sánh;
- Toán tử logic;
- Toán tử kiểm tra kiểu;
- Toán tử bit (nhị phân).
4.1. Toán tử gán
Toán tử gán dùng để gán giá trị cho một biến. Ngoài phép gán đơn giản =, còn có các phép gán kết hợp phép tính (Bảng 5-2):
| Toán tử | Ví dụ | Tương đương với |
|---|---|---|
= |
x = y |
x = y |
+= |
x += y |
x = x + y |
-= |
x -= y |
x = x - y |
*= |
x *= y |
x = x * y |
/= |
x /= y |
x = x / y |
%= |
x %= y |
x = x % y |
let x = 10;
x += 5; console.log("x += 5 ->", x); // 15
x -= 3; console.log("x -= 3 ->", x); // 12
x *= 2; console.log("x *= 2 ->", x); // 24
x /= 4; console.log("x /= 4 ->", x); // 6
x %= 4; console.log("x %= 4 ->", x); // 24.2. Toán tử toán học
Toán tử toán học thực hiện các phép tính đơn giản trên các toán hạng (Bảng 5-3):
| Toán tử | Mô tả |
|---|---|
+ |
Cộng |
- |
Trừ |
* |
Nhân |
** |
Lũy thừa |
/ |
Chia |
% |
Chia lấy phần dư |
++ |
Tăng một đơn vị |
-- |
Giảm một đơn vị |
let a = 17, b = 5;
console.log("a + b =", a + b); // 22
console.log("a - b =", a - b); // 12
console.log("a * b =", a * b); // 85
console.log("a / b =", a / b); // 3.4 (chia thực, không phải chia nguyên)
console.log("a % b =", a % b); // 2 (phần dư)
console.log("a ** 2 =", a ** 2); // 289 (lũy thừa)
console.log("Phần nguyên của a / b:", Math.floor(a / b)); // 3
console.log("-7 % 3 =", -7 % 3); // -1: dấu của số dư theo số bị chiaToán tử ++ và -- tăng/giảm biến đi 1. Có hai cách viết: đặt trước (++i: tăng rồi mới dùng giá trị) và đặt sau (i++: dùng giá trị cũ rồi mới tăng):
let i = 5;
console.log(i++); // 5 (in giá trị cũ, sau đó i thành 6)
console.log(i); // 6
console.log(++i); // 7 (tăng trước rồi mới in)
console.log(i--); // 7 (in giá trị cũ, sau đó i thành 6)
console.log(i); // 6Thứ tự ưu tiên của toán tử giống toán học: ** trước, rồi * / %, rồi + -; dùng dấu ngoặc ( ) để đổi thứ tự hoặc cho rõ ràng:
console.log(2 + 3 * 4); // 14 (nhân trước)
console.log((2 + 3) * 4); // 20 (ngoặc trước)
console.log(2 ** 3 ** 2); // 512 (lũy thừa kết hợp từ phải sang trái: 2 ** 9)
console.log(10 - 4 - 3); // 3 (trừ kết hợp từ trái sang phải)Toán tử với chuỗi. Dấu + có hai vai trò: cộng số khi cả hai toán hạng là số, và ghép chuỗi (concatenation) khi có ít nhất một toán hạng là chuỗi (toán hạng còn lại bị ép thành chuỗi). Toán tử += cũng dùng để nối thêm vào chuỗi:
console.log("Xin chào " + "Web"); // Xin chào Web
console.log(2 + "2"); // "22" (ghép chuỗi, không phải 4)
console.log(2 + 2 + "2"); // "42" (tính từ trái sang phải: 2 + 2 = 4, rồi "4" + "2")
console.log("2" + 2 + 2); // "222"
console.log("5" - 2); // 3 (trừ chỉ làm việc với số nên "5" bị ép thành số)
console.log("5" * "2"); // 10
let loichao = "Xin chào";
loichao += ", Minh!";
console.log(loichao);Cảnh báo:
2 + "2"cho"22"chứ không phải4(đây cũng chính là Câu 6 trong phần ôn tập). Quy tắc nhớ nhanh:+thấy chuỗi thì ghép chuỗi;-,*,/luôn tính số. Khi lấy dữ liệu từ ô nhập, hãyNumber(...)trước khi cộng.
4.3. Toán tử so sánh
Toán tử so sánh dùng để so sánh hai toán hạng và cho kết quả đúng/sai (Bảng 5-4):
| Toán tử | Mô tả |
|---|---|
== |
Bằng giá trị |
=== |
Bằng giá trị và cùng kiểu dữ liệu |
!= |
Khác giá trị |
!== |
Không bằng giá trị hoặc kiểu dữ liệu |
> |
Lớn hơn |
< |
Nhỏ hơn |
>= |
Lớn hơn hoặc bằng |
<= |
Nhỏ hơn hoặc bằng |
Khác biệt cốt lõi: == ép kiểu rồi so sánh giá trị, còn === không ép kiểu: khác kiểu là false ngay.
console.log(5 == "5"); // true (ép chuỗi "5" thành số 5)
console.log(5 === "5"); // false (khác kiểu: số và chuỗi)
console.log(5 != "5"); // false
console.log(5 !== "5"); // true
console.log(0 == false); // true
console.log(0 === false); // false
console.log("" == 0); // true
console.log(null == undefined); // true
console.log(null === undefined); // false
console.log(NaN == NaN); // false (NaN không bằng bất cứ gì, kể cả chính nó)
console.log(3 >= 3, 2 <= 1, 10 > 9, 4 < 4);Ép kiểu: == so với === và các toán tử
chọn hai giá trị mẫu và một phép toán- 1Số so với chuỗi → chuỗi đổi sang số: Number("0") = 0.
- 2So sánh lại: 0 == 0
- 3Cùng kiểu number → so sánh thẳng giá trị 0 với 0.
| Giá trị | Kiểu | Number(x) | Boolean(x) | String(x) |
|---|---|---|---|---|
| 0 | number | 0 | false | "0" |
| "0" | string | 0 | true | "0" |
Vế trái: số (number); vế phải: chuỗi (string). Giá trị falsy gồm 0, "", false, null, undefined, NaN.
| 0 | 1 | "0" | "1" | "" | "abc" | true | false | null | undef | NaN | [] | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | ||||||||||||
| 1 | ||||||||||||
| "0" | ||||||||||||
| "1" | ||||||||||||
| "" | ||||||||||||
| "abc" | ||||||||||||
| true | ||||||||||||
| false | ||||||||||||
| null | ||||||||||||
| undef | ||||||||||||
| NaN | ||||||||||||
| [] |
💡 Nhìn ma trận ==: rất nhiều ô “true” khó đoán. Chuyển sang === để thấy chỉ các giá trị cùng kiểu và cùng giá trị mới bằng nhau → nên dùng === và !== trong mã thực tế.
Mô phỏng trên cho bạn nhập hai giá trị bất kỳ rồi so sánh bằng == và ===, đồng thời cho thấy giá trị được ép sang kiểu nào. Hãy thử các cặp "kinh điển": 0 với "", "0" với false, null với undefined.
Mẹo: Hãy mặc định dùng
===và!==— chúng dễ đoán, không có bất ngờ do ép kiểu. Chỉ dùng==khi bạn thực sự muốn ép kiểu và hiểu rõ kết quả (ví dụx == nullđể kiểm tra cùng lúcnullvàundefined). Giáo trình cũng kèm một công cụ trực quan "So sánh và = trong JavaScript" trên trang fit.neu.edu.vn/smartdoc để bạn tra cứu thêm.
Các phép so sánh quan hệ (<, >...) cũng ép kiểu. Hai chuỗi được so sánh theo thứ tự từ điển (từng ký tự), còn nếu một bên là số thì bên kia được ép thành số:
console.log("10" < "9"); // true: so sánh chuỗi, "1" đứng trước "9"
console.log("10" < 9); // false: "10" được ép thành số 10
console.log("abc" < "abd"); // true
console.log("a" < "B"); // false: chữ thường có mã lớn hơn chữ hoa
console.log(null >= 0); // true (null được ép thành 0)
console.log(undefined > 0, undefined == 0); // false false4.4. Toán tử logic
Toán tử logic dùng để ghép nhiều điều kiện lại với nhau, thường gặp trong câu lệnh if và vòng lặp. JavaScript có ba toán tử logic:
&&(và): trả vềtruekhi cả hai vế đều đúng.||(hoặc): trả vềtruekhi có ít nhất một vế đúng.!(phủ định): đảo ngược giá trị đúng/sai của biểu thức.
let tuoi = 20;
let cothe = true;
console.log(tuoi >= 18 && cothe); // true
console.log(tuoi < 18 || cothe); // true
console.log(!cothe); // falseBảng chân trị của ba toán tử (với p, q là hai điều kiện; cột thứ ba ứng với &&, thứ tư ứng với hai gạch đứng ||, thứ năm ứng với !):
| p | q | p và q (AND) | p hoặc q (OR) | không p (NOT) |
|---|---|---|---|---|
| true | true | true | true | false |
| true | false | false | true | false |
| false | true | false | true | true |
| false | false | false | false | true |
Hai toán tử && và || được tính theo lối rút gọn (short-circuit): nếu vế trái đã đủ quyết định kết quả thì vế phải không được thực hiện. Nhờ đặc điểm này, || thường dùng để đặt giá trị mặc định, còn && dùng để chỉ chạy một lệnh khi điều kiện thỏa mãn:
let nhapTen = ""; // Người dùng chưa nhập tên
let ten = nhapTen || "Khách"; // "" là falsy nên lấy vế phải
console.log(ten); // Khách
let dangnhap = true;
function hienThiMenu() { console.log("Menu"); }
dangnhap && hienThiMenu(); // Chỉ gọi hienThiMenu() khi dangnhap là true
dangnhap = false;
dangnhap && hienThiMenu(); // vế trái false => vế phải không chạy
console.log("Xong");Thực chất, && và || không nhất thiết trả về true/false mà trả về một trong hai toán hạng: a || b cho a nếu a là truthy, ngược lại cho b; a && b cho a nếu a là falsy, ngược lại cho b. Đây cũng là cơ sở của bài "Giá trị truthy đầu tiên" ở phần luyện tập:
console.log(0 || "mặc định"); // "mặc định"
console.log("A" || "B"); // "A"
console.log(null || undefined || 7); // 7: giá trị truthy đầu tiên
console.log(1 && "có"); // "có"
console.log(0 && "có"); // 0
console.log(!!"abc", !!"", !!0, !![]); // true false false trueLưu ý:
!!giatri(hai dấu phủ định liên tiếp) là mẹo quen thuộc để chuyển một giá trị bất kỳ thànhtrue/false, tương đươngBoolean(giatri).
4.5. Toán tử kiểm tra kiểu
Hai toán tử kiểm tra kiểu là typeof và instanceof đã được trình bày ở mục 3.6. Cần lưu ý thêm: typeof của một mảng trả về "object", nên muốn biết một giá trị có phải mảng hay không thì phải dùng Array.isArray():
const ds = [1, 2, 3];
console.log(typeof ds); // "object"
console.log(Array.isArray(ds)); // true
console.log(Array.isArray("abc")); // false
console.log(ds instanceof Array); // true
console.log(typeof "abc", typeof 3);4.6. Toán tử nhị phân
Toán tử bit (bitwise, còn gọi toán tử nhị phân) làm việc trên biểu diễn nhị phân của các số, dưới dạng số nguyên 32 bit có dấu. Với số thuộc hệ khác, các toán hạng được chuyển sang hệ nhị phân trước khi tính (Bảng 5-5, giáo trình minh họa bằng 4 bit thấp cho gọn):
&(AND):5 & 1=0101 & 0001=0001(1)|(OR):5 | 1=0101 | 0001=0101(5)~(NOT):~5= đảo mọi bit của0101; với 4 bit thấp là1010^(XOR):5 ^ 1=0101 ^ 0001=0100(4)<<(dịch trái):5 << 1=0101dịch trái 1 bit =1010(10)>>(dịch phải):5 >> 1=0101dịch phải 1 bit =0010(2)
const bin = (n) => (n >>> 0).toString(2).padStart(4, "0").slice(-4); // 4 bit thấp để dễ nhìn
console.log("5 & 1 =", 5 & 1, "|", bin(5), "&", bin(1), "=", bin(5 & 1));
console.log("5 | 1 =", 5 | 1, "|", bin(5), "|", bin(1), "=", bin(5 | 1));
console.log("5 ^ 1 =", 5 ^ 1, "|", bin(5), "^", bin(1), "=", bin(5 ^ 1));
console.log("~5 =", ~5, "| 4 bit thấp:", bin(~5));
console.log("5 << 1 =", 5 << 1, "|", bin(5), "<< 1 =", bin(5 << 1));
console.log("5 >> 1 =", 5 >> 1, "|", bin(5), ">> 1 =", bin(5 >> 1));Lưu ý ~5 bằng -6 chứ không phải 10: vì số là 32 bit có dấu, đảo toàn bộ 32 bit của 5 ra số âm (quy tắc bù hai: ~n = -n - 1). Con số 1010 trong bảng giáo trình chính là 4 bit thấp của kết quả.
Mở rộng: Toán tử bit rất tiện cho các bài toán nhỏ:
n & 1cho biếtnchẵn (0) hay lẻ (1);(n >> k) & 1lấy bit thứk;n << 1nhân đôi;a ^ bdùng trong hoán đổi/mã hóa đơn giản. Đừng nhầm&/|(toán tử bit) với&&/||(toán tử logic).
Mở rộng: Bạn cũng có thể gặp thêm các toán tử rất thông dụng trong mã hiện đại tuy giáo trình chưa nhắc: toán tử ba ngôi
điều_kiện ? giá_trị_1 : giá_trị_2, toán tử??(chỉ lấy vế phải khi vế trái lànull/undefined) và?.(truy cập an toàn thuộc tính). Chúng sẽ xuất hiện dần ở các buổi sau.
Vừa học xong các nhóm toán tử, hãy làm các bài nhóm 10.3: so sánh == với ===, cộng chuỗi và số, rút gọn ||/&&, toán tử bit và biểu thức ép kiểu hỗn hợp.
5. Hàm
Giáo trình: Chương 5 — mục "Hàm" (Khai báo và sử dụng hàm; Hàm mũi tên; Các hàm của các đối tượng có sẵn).
5.1. Khai báo và sử dụng hàm
Một hàm (function) là một khối mã lệnh được thiết kế để thực hiện một công việc cụ thể. Viết một lần, gọi nhiều lần — đó là lý do hàm giúp mã ngắn gọn và dễ bảo trì. Theo cách truyền thống, hàm được khai báo bằng từ khóa function, tiếp theo là tên hàm và cặp dấu () chứa các tham số:
function tinhtong(a, b) {
return a + b;
}
var tong = tinhtong(5, 10);
console.log(tong); // 15
console.log(tinhtong(1.5, 2.5), tinhtong("Xin ", "chào")); // 4 "Xin chào"Các tham số (parameter) đặt trong () được truyền giá trị (gọi là đối số — argument) khi gọi hàm. Hàm trả về giá trị thông qua lệnh return. Khi gặp return, hàm kết thúc việc thực thi ngay và giá trị được trả về nơi hàm được gọi — mọi lệnh phía sau return không bao giờ chạy. Hàm không có return (hoặc return rỗng) trả về undefined:
function chao(ten) {
console.log("Xin chào " + ten);
return "đã chào " + ten;
console.log("Dòng này không bao giờ chạy"); // sau return
}
var kq = chao("Minh");
console.log(kq);
function khongTraVe() {
var x = 1; // không có return
}
console.log(khongTraVe()); // undefinedThiếu đối số khi gọi thì tham số tương ứng nhận undefined; thừa đối số thì các đối số thừa bị bỏ qua:
function tinhtong(a, b) {
return a + b;
}
console.log(tinhtong(5)); // NaN: b là undefined, 5 + undefined = NaN
console.log(tinhtong(1, 2, 3)); // 3: đối số thứ ba bị bỏ quaLưu ý quan trọng: các biến khai báo trong hàm là biến cục bộ (local) và chỉ truy cập được bên trong hàm; chúng được tạo khi hàm bắt đầu thực thi và bị xóa khi hàm kết thúc (đúng như mục 2.2 đã nói). Tham số của hàm cũng là biến cục bộ:
function dientich(dai, rong) {
var kq = dai * rong; // kq, dai, rong đều là biến cục bộ
return kq;
}
console.log(dientich(8, 5)); // 40
console.log(typeof kq, typeof dai); // undefined undefined: ngoài hàm không thấyHàm: tham số, giá trị trả về, phạm vi
chọn kịch bản, bấm Bước tiếp để theo dõi ngăn xếpMô phỏng trên cho bạn thay đổi tham số, thân hàm và quan sát giá trị trả về cùng các biến cục bộ được tạo ra/bị xóa theo từng bước. Hãy thử gọi hàm với thiếu và thừa đối số.
Mẹo: Đặt tên hàm là một động từ nêu việc nó làm (
tinhTong,dinhDangTien,kiemTraEmail) và để mỗi hàm chỉ làm một việc. Hàm nhận dữ liệu qua tham số và trả kết quả quareturn(thay vì tựconsole.log) sẽ dễ tái sử dụng và dễ kiểm thử hơn.
Mở rộng: Khai báo hàm bằng
functioncũng được nâng (hoisting) — bạn có thể gọi hàm trước dòng khai báo, đó là lý do ví dụmyInfo()ở mục 2.2 chạy đúng. Ngoài ra tham số có thể có giá trị mặc định:function chao(ten = "Khách") { ... }.
5.2. Hàm mũi tên
Bên cạnh cách khai báo truyền thống bằng function, đặc tả ES6 cho phép dùng hàm mũi tên (arrow function). Hàm truyền thống:
function tinhtong(a, b) {
return a + b;
}chuyển thành hàm mũi tên như sau:
let tinhtong = (a, b) => {
return a + b;
}Vì thân hàm chỉ có một câu lệnh trả về giá trị, ta có thể rút gọn: bỏ cặp ngoặc nhọn và từ khóa return (giá trị của biểu thức được tự động trả về):
let tinhtong = (a, b) => a + b;
console.log(tinhtong(5, 10)); // 15
const binhphuong = x => x * x; // một tham số: có thể bỏ ngoặc ()
const chao = () => "Xin chào!"; // không tham số: dùng () trống
console.log(binhphuong(7), chao()); // 49 "Xin chào!"
const doiTien = (usd, tigia = 25000) => usd * tigia; // tham số mặc định
console.log(doiTien(2)); // 50000Hàm mũi tên cho phép khai báo một số hàm với cú pháp ngắn hơn, rất hợp khi truyền hàm làm đối số (như arr.map(s => s.trim()) ở mục 3.2). Tuy nhiên cần lưu ý khi dùng this:
- Với hàm khai báo bằng
function,thislà đối tượng gọi hàm. - Hàm mũi tên không có
thisriêng: nó dùng lạithiscủa phạm vi bao quanh nơi hàm được định nghĩa.
const sv1 = {
name: "Dương Minh",
ham1: function () {
console.log("ham1: this là sv1?", this === sv1); // true
let ham2 = () => console.log("ham2: this là sv1?", this === sv1); // true (mượn this của ham1)
ham2();
},
ham3: () => console.log("ham3: this là sv1?", this === sv1) // false
};
sv1.ham1();
sv1.ham3();Trong ví dụ này, this trong ham1() và ham2() chính là đối tượng sv1. Còn this trong ham3() phụ thuộc vào việc ham3() được định nghĩa ở đâu: nếu mã ở mức cao nhất của trang trong trình duyệt thì this đó sẽ là đối tượng Window — không phải sv1. Trong giáo trình, Hình 5-2 (kết quả trên cửa sổ Console của DevTools) cho thấy chính điều này: hai dòng đầu in ra đối tượng sv1, dòng cuối in ra Window.
| Tiêu chí | Hàm function |
Hàm mũi tên |
|---|---|---|
| Cú pháp | function ten(a, b) { return a + b; } |
(a, b) => a + b |
this |
Đối tượng gọi hàm | Dùng this của phạm vi bao quanh |
| Dùng làm phương thức của đối tượng | Phù hợp | Cẩn thận: this không phải đối tượng |
| Dùng làm đối số ngắn gọn | Dài dòng | Rất gọn |
Cảnh báo: Đừng dùng hàm mũi tên để định nghĩa phương thức của đối tượng nếu bên trong cần
thistrỏ về chính đối tượng (nhưham3ở trên). Với phương thức, hãy dùngfunction(hoặc cú pháp rút gọnham() { ... }).
5.3. Các hàm của các đối tượng có sẵn
JavaScript cung cấp nhiều lớp có sẵn kèm theo các hàm rất hữu ích. Ví dụ, đối tượng Math cung cấp nhiều hàm toán học:
console.log(Math.round(9.1)); // 9: làm tròn
console.log(Math.pow(2, 3)); // 8: 2 mũ 3
console.log(Math.sqrt(9)); // 3: căn bậc hai
console.log(Math.ceil(9.4)); // 10: làm tròn lên
console.log(Math.floor(9.9)); // 9: làm tròn xuống
console.log(Math.max(4, 9, 2), Math.min(4, 9, 2)); // 9 2
console.log(Math.abs(-4)); // 4: giá trị tuyệt đối
const r = Math.random(); // một số ngẫu nhiên trong khoảng [0, 1)
console.log(r >= 0 && r < 1); // true (mỗi lần chạy giá trị r khác nhau)
const xucxac = Math.floor(Math.random() * 6) + 1; // số nguyên ngẫu nhiên 1..6
console.log(xucxac >= 1 && xucxac <= 6);Lưu ý:
Math.round(2.5)cho3nhưngMath.round(-2.5)cho-2(làm tròn "về phía dương" ở giá trị nửa). Hãy nhớ khi làm bài toán làm tròn, và khi cần hiển thị 2 chữ số thập phân hãy dùngtoFixed(2).
Lớp Array cũng cung cấp nhiều phương thức để làm việc với mảng:
console.log(Array.of(1, 2, 3)); // [1, 2, 3]
console.log(Array.from(new Set(['Công', 'Nghệ', 'Thông', 'Tin', 'Công']))); // Set loại phần tử trùng
console.log(Array.from("abc")); // ['a', 'b', 'c']
console.log(Array.isArray([])); // trueNgoài ra còn rất nhiều lớp hữu ích khác như lớp JSON để xử lý dữ liệu JSON, lớp Function để làm việc với hàm, lớp Error để xử lý lỗi... Mảng, đối tượng và xử lý lỗi sẽ học ở buổi 11, còn JSON ở buổi 13.
Mở rộng: Hàm trong JavaScript là "công dân hạng nhất": có thể gán vào biến, truyền làm đối số, và trả về từ hàm khác. Một hàm khai báo bên trong hàm khác vẫn "nhớ" các biến cục bộ của hàm bao ngoài, kể cả khi hàm bao ngoài đã chạy xong (gọi là closure):
function taoBoDem(buoc) {
let dem = 0; // biến cục bộ của taoBoDem
return function () { // hàm bên trong "nhớ" dem và buoc
dem += buoc;
return dem;
};
}
const demLen = taoBoDem(2);
console.log(demLen(), demLen(), demLen()); // 2 4 6
const demKhac = taoBoDem(10);
console.log(demKhac(), demLen()); // 10 8 (mỗi bộ đếm có "dem" riêng)Vừa học xong cách khai báo hàm, return, hàm mũi tên và các hàm có sẵn của Math, hãy làm các bài nhóm 10.4: viết hàm tính toán, hàm chuyển đổi đơn vị và vài bài đệ quy/closure nâng cao.
Thực hành theo giáo trình
Giáo trình: Chương 5 — mục "Thực hành" và "Bài tập tự thực hành" (Bài 5.1–5.5). Buổi này làm Bài 5.4; các Bài 5.1 (quản lý danh sách công việc), 5.2 (trò chơi đoán số), 5.3 (thông tin sinh viên) cần mảng, câu điều kiện, lớp (class) nên được thực hiện ở buổi 11, cùng Bài 5.5 (đếm số lần xuất hiện của các từ).
Bài 5.4. Tính toán diện tích hình chữ nhật
Mục tiêu: vận dụng biến, kiểu dữ liệu, toán tử và hàm để viết chương trình tính diện tích hình chữ nhật khi người dùng nhập chiều dài và chiều rộng.
Yêu cầu của giáo trình:
- Chương trình yêu cầu người dùng nhập vào chiều dài và chiều rộng của hình chữ nhật.
- Sau đó tính toán và hiển thị diện tích.
- Xử lý các trường hợp lỗi nhập liệu (nhập giá trị không phải số, hoặc giá trị âm).
Các bước thực hiện:
- Dùng
prompt()để lấy hai giá trị nhập (kết quả luôn là chuỗi). - Chuyển sang số bằng
Number()(hoặcparseFloat()). - Kiểm tra hợp lệ: không phải
NaNvà lớn hơn 0 (dùngNumber.isNaN()và toán tử so sánh/logic). - Đặt phép tính trong một hàm
dienTich(dai, rong)trả về kết quả. - Hiển thị kết quả (bằng
alert()hoặcconsole.log()).
Trong khối chạy thử, các dòng // @prompt thay cho việc bạn gõ vào hộp thoại: giá trị lần lượt là chiều dài và chiều rộng. Hãy đổi chúng thành abc hoặc -3 để xem chương trình báo lỗi:
function dienTich(dai, rong) {
return dai * rong;
}
function hopLe(x) {
return !Number.isNaN(x) && x > 0;
}
const dai = Number(prompt("Nhập chiều dài:"));
const rong = Number(prompt("Nhập chiều rộng:"));
if (!hopLe(dai) || !hopLe(rong)) {
alert("Lỗi: chiều dài và chiều rộng phải là số dương!");
} else {
alert("Diện tích hình chữ nhật = " + dienTich(dai, rong));
}Với dữ liệu mẫu trên, kết quả là 34. Chú ý: Number("") cho 0 (chuỗi rỗng bị coi là 0) nên điều kiện x > 0 giúp loại luôn trường hợp người dùng bỏ trống ô nhập.
Thử thêm một phiên bản dùng hàm mũi tên và kiểm tra nhiều bộ dữ liệu cùng lúc (không cần prompt):
const hopLe = (x) => !Number.isNaN(x) && x > 0;
const dienTich = (dai, rong) => dai * rong;
const tests = [["8.5", "4"], ["abc", "4"], ["-3", "5"], ["", "5"], ["10", "2.5"]];
for (const [d, r] of tests) {
const a = Number(d), b = Number(r);
console.log(`dài="${d}", rộng="${r}" ->`, hopLe(a) && hopLe(b) ? dienTich(a, b) : "dữ liệu không hợp lệ");
}(Chương trình không in gì.)
Chạy trong khung cách ly ngay trên trình duyệt của bạn; không có mạng.
Hãy mở trình soạn ở trên, dán chương trình Bài 5.4 vào và thử với các dữ liệu nhập khác nhau.
Câu hỏi ôn tập
Dưới đây là các câu hỏi ôn tập lý thuyết của Chương 5 thuộc nội dung buổi này (các Câu 3, 4 về câu điều kiện if và Câu 9, 10 về mảng sẽ được ôn ở buổi 11).
Câu 1. Làm cách nào để tạo một hàm? A. function:myFunction() · B. function = myFunction() · C. function myFunction() · D. function:=myFunction()
Đáp án: C. Cú pháp khai báo hàm là từ khóa function, theo sau là tên hàm và cặp ngoặc ().
Câu 2. Làm thế nào để gọi một hàm có tên là "myFunction"? A. call function myFunction() · B. myFunction() · C. call myFunction() · D. function.myFunction()
Đáp án: B. Gọi hàm bằng tên hàm kèm dấu ngoặc: myFunction().
Câu 5. Từ khóa nào được sử dụng để khai báo biến? A. let · B. var · C. const · D. Tất cả phương án trên đều đúng
Đáp án: D. Cả var, let và const đều khai báo biến (const khai báo hằng, không gán lại được).
Câu 6. Kết quả của lệnh console.log(2 + "2") là gì? A. 4 · B. "22" · C. NaN · D. "2 + 2"
Đáp án: B. Toán tử + gặp một toán hạng là chuỗi nên ghép chuỗi: "22".
Câu 7. Sử dụng toán tử nào để so sánh cả giá trị và kiểu dữ liệu? A. == · B. === · C. != · D. <>
Đáp án: B. === so sánh chặt: bằng giá trị và cùng kiểu (<> không tồn tại trong JavaScript).
Câu 8. Thuộc tính nào được sử dụng để lấy độ dài của một chuỗi? A. length · B. size() · C. count() · D. strlen()
Đáp án: A. Dùng thuộc tính length (không có dấu ngoặc), ví dụ "Việt Nam".length bằng 8.
Câu bổ sung (tự luyện). Điền kết quả: typeof null, typeof NaN, "5" - 2, "5" + 2, 0 == "", 0 === "".
Đáp án: "object", "number", 3, "52", true, false. Hãy tự chạy lại trên trình duyệt để kiểm chứng.
Sai lầm thường gặp
| Sai lầm | Hậu quả | Cách đúng |
|---|---|---|
Cộng số đọc từ ô nhập mà không đổi kiểu: "3" + 1 |
Ra chuỗi "31" thay vì 4 |
Dùng Number("3") + 1 hoặc parseInt/parseFloat trước khi cộng |
Dùng == để so sánh |
Bị ép kiểu bất ngờ (0 == "" là true) |
Mặc định dùng === và !== |
Viết if (x = 5) thay vì x === 5 |
Phép gán chứ không phải so sánh, điều kiện luôn đúng | Dùng === để so sánh; = chỉ để gán |
| Gán cho biến chưa khai báo trong hàm | Âm thầm tạo biến toàn cục, dễ gây xung đột | Luôn khai báo bằng let/const; có thể bật "use strict" |
Cho rằng biến var trong khối { } chỉ có tác dụng trong khối |
Biến "rò" ra ngoài khối, giá trị bị ghi đè bất ngờ | Dùng let/const để có phạm vi khối |
Dùng biến let/const trước dòng khai báo |
Lỗi ReferenceError (vùng chết tạm thời) |
Khai báo ở đầu phạm vi, trước khi dùng |
Gán lại biến const |
Lỗi TypeError; hoặc hiểu nhầm const làm đối tượng bất biến |
Dùng let nếu cần gán lại; nhớ const chỉ khóa tham chiếu |
Kiểm tra x === NaN |
Luôn false vì NaN không bằng chính nó |
Dùng Number.isNaN(x) |
Dùng typeof để nhận biết mảng hoặc null |
Cho "object", dễ nhận sai |
Dùng Array.isArray(x) và x === null |
So sánh số thực bằng ===: 0.1 + 0.2 === 0.3 |
Là false do sai số số thực nhị phân |
Làm tròn (toFixed) hoặc tính theo đơn vị nguyên |
Quên return trong hàm hoặc đặt lệnh sau return |
Hàm trả về undefined; lệnh phía sau không chạy |
Đảm bảo return là lệnh cuối của nhánh cần trả giá trị |
Dùng hàm mũi tên làm phương thức có this |
this không phải đối tượng mong muốn |
Dùng function hoặc cú pháp phương thức rút gọn |
Đặt tên biến trùng từ khóa hoặc quên phân biệt hoa/thường (Name khác name) |
Lỗi cú pháp hoặc truy cập nhầm biến | Tránh từ khóa, thống nhất một quy ước đặt tên (ví dụ camelCase) |
Gọi document.write() sau khi trang đã tải |
Xóa sạch nội dung trang | Dùng innerText/innerHTML hoặc console.log |
Tóm tắt
- JavaScript là ngôn ngữ lập trình thứ ba (cùng HTML, CSS) của Web, chạy ở trình duyệt và máy chủ (Node.js); chèn mã bằng thẻ
<script>(trực tiếp hoặc tệp.jsquasrc), dự phòng bằng<noscript>. - Xem kết quả bằng
console.log()(F12 → Console); chú thích bằng//và/* */; không dùng từ khóa/từ dành riêng để đặt tên. - Biến:
var(cũ),let,const; tên gồm chữ, số,_,$, không bắt đầu bằng số, phân biệt hoa/thường; kiểu dữ liệu động. - Phạm vi: cục bộ (trong hàm) và toàn cục;
let/constcó phạm vi khối;constchỉ khóa tham chiếu; hoisting chỉ nâng phần khai báo,let/constcó vùng chết tạm thời. - Kiểu dữ liệu: số (một kiểu 64 bit,
NaN,Infinity), chuỗi (length,indexOf,replace,trim,split...), boolean, đối tượng,null,undefined; kiểm tra bằngtypeof/instanceof; chuyển kiểu bằngString(),Number(),Boolean(),parseInt(),parseFloat(). - Toán tử: gán (
=,+=...), toán học (+ - * ** / % ++ --), so sánh (==khác===), logic (&&,||,!với rút gọn), kiểm tra kiểu, bit. - Hàm: khai báo bằng
functionhoặc hàm mũi tên=>; tham số,return, biến cục bộ;thiscủa hàm mũi tên lấy từ phạm vi bao quanh; dùng sẵn các hàm củaMath,Array...