Giống như hầu hết mọi ngôn ngữ lập trình, JavaScript xử lý các loại giá trị khác nhau theo những cách riêng biệt. Một chuỗi (String) như “Hello world” rất khác biệt so với một số (Number) như 42. Nhưng có những kiểu dữ liệu nào trong JavaScript, và tại sao việc hiểu rõ chúng lại quan trọng đối với mỗi lập trình viên hay người học phát triển web?
Trong bài viết này, chúng ta sẽ đi sâu vào thế giới của các kiểu dữ liệu JavaScript, từ những loại cơ bản nhất đến những khái niệm phức tạp hơn như ép kiểu (type coercion) và biến động kiểu (dynamic typing). Nắm vững kiến thức này không chỉ giúp bạn viết code chính xác hơn mà còn là nền tảng vững chắc để xây dựng các ứng dụng web mạnh mẽ và hiệu quả trên nền tảng JavaScript.
Các Kiểu Dữ Liệu Chính Trong JavaScript
Mỗi giá trị trong JavaScript đều sở hữu một kiểu dữ liệu cụ thể, tổng cộng có chín loại cơ bản:
- Number (Số)
- String (Chuỗi)
- Boolean (Logic)
- Null (Rỗng)
- Undefined (Không xác định)
- BigInt (Số nguyên lớn)
- Symbol (Ký hiệu độc nhất)
- Object (Đối tượng)
- Function (Hàm)
Khi làm việc với một giá trị, ngôn ngữ JavaScript thường kiểm tra xem nó có đúng kiểu hay không. Tùy thuộc vào trường hợp cụ thể, một giá trị với kiểu khác có thể được xử lý tự động (ép kiểu) hoặc có thể gây ra lỗi.
Undefined: Khi Biến Chưa Được Gán Giá Trị
Một biến chưa được gán giá trị sẽ có giá trị là undefined
. Ví dụ:
> var foo;
undefined
Boolean: Giá Trị Logic Đúng/Sai
Giá trị Boolean chỉ có thể là true
(đúng) hoặc false
(sai) và thường được sử dụng trong các điều kiện logic để điều khiển luồng chương trình.
String: Đại Diện Cho Văn Bản
Kiểu String lưu trữ các giá trị văn bản như “hello”, “$$$”, và “Vài từ bất kỳ.” Chuỗi trong JavaScript được lưu trữ dưới định dạng UTF-16. Điều này có nghĩa là bạn có thể sử dụng các ký tự Unicode, bao gồm cả biểu tượng cảm xúc (emoji), trong các chuỗi của mình một cách dễ dàng:
alert("🙂");
Mọi thứ sẽ hoạt động như mong đợi:
Hộp thoại alert trong Chrome hiển thị biểu tượng emoji mặt cười, minh họa khả năng lưu trữ ký tự Unicode của kiểu dữ liệu String trong JavaScript.
Number và BigInt: Xử Lý Số Học
Không giống nhiều ngôn ngữ khác, JavaScript không phân biệt giữa số nguyên (integers) và số thực dấu phẩy động (floating-point numbers). Cả 42
và 3.14
đều thuộc kiểu Number.
Trong khi đó, BigInt, đúng như tên gọi của nó, chỉ bao gồm các số nguyên, và chỉ những số có giá trị vượt quá khả năng lưu trữ của kiểu Number. Giá trị BigInt có thể cực kỳ lớn. Hãy thử chạy đoạn code sau trong console để lấp đầy trang hiện tại của bạn bằng một số rất lớn:
b = document.body, b.style.overflowWrap = "break-word", b.textContent = BigInt(2) ** BigInt(200000);
Null: Sự Vắng Mặt Của Giá Trị
Null
là một giá trị đặc biệt được sử dụng để biểu thị “sự vắng mặt của bất kỳ giá trị nào.” Điều này nghe có vẻ mâu thuẫn, nhưng nó rất hữu ích trong lập trình hướng đối tượng, nơi các tham chiếu đối tượng có thể đại diện cho các mối quan hệ:
var anne = { name: "Anne", child: null };
var john = { name: "John", child: child };
Symbol: Tính Năng Cao Cấp Của Ngôn Ngữ
Symbol là một tính năng ít được biết đến ở cấp độ ngôn ngữ mà có thể bạn sẽ không bao giờ cần hiểu tường tận. Nếu tò mò, bạn có thể tham khảo tài liệu MDN về Symbol.
Object và Function: Kiểu Dữ Liệu Phức Hợp
Function
(Hàm) và Object
(Đối tượng) là các giá trị mà bạn có thể tạo bằng nhiều cơ chế khác nhau, bao gồm cú pháp literal của ngôn ngữ:
function my_function() { /* ... */ }
let my_object = {};
Bạn cũng có thể tạo một giá trị Function bằng cách sử dụng biểu thức:
my_function = function() { /* ... */ };
Và bạn có thể tạo một Object bằng một constructor:
let answer = new Object(42);
Cơ Chế Hoạt Động Của Kiểu Dữ Liệu Trong JavaScript
Biến Động Kiểu (Dynamic Typing) Trong JavaScript
Đầu tiên, điều quan trọng cần lưu ý là các giá trị của JavaScript có kiểu cố định, nhưng các biến thì không. Một biến JavaScript là biến động kiểu (dynamically typed), nghĩa là nó sẽ tự động nhận kiểu của giá trị mà nó đang giữ:
> var foo = 1;
> typeof foo; // 'number'
> foo = "hello";
> typeof foo; // 'string'
Toán tử typeof
trả về một chuỗi chứa kiểu của toán hạng của nó, giúp bạn kiểm tra kiểu của một biến.
So Sánh Giá Trị và Ép Kiểu (Type Coercion)
Một trong những tác động lớn nhất mà kiểu dữ liệu có thể gây ra là khi so sánh hai giá trị. JavaScript có hai toán tử so sánh bằng: ==
và ===
. Chúng lần lượt kiểm tra “bằng lỏng lẻo” (loose equality) và “bằng nghiêm ngặt” (strict equality), và sự khác biệt nằm ở kiểu dữ liệu:
function check_equals(a, b) {
if (a === b) {
console.log("a và b bằng nghiêm ngặt");
} else if (a == b) {
console.log("a và b bằng lỏng lẻo");
} else {
console.log("a và b không bằng nhau chút nào");
}
}
Nếu bạn gọi check_equals(42, 42)
, bạn sẽ thấy hai giá trị bằng nghiêm ngặt: chúng có cùng giá trị và cùng kiểu. Nhưng check_equals(42, "42")
sẽ cho thấy rằng, mặc dù một số và một chuỗi có thể đánh giá ra cùng một giá trị, nhưng chúng vẫn có các kiểu khác nhau, vì vậy chúng chỉ bằng lỏng lẻo.
Kiểm tra bằng lỏng lẻo là một ví dụ về ép kiểu (type coercion): sự chuyển đổi tự động một giá trị từ kiểu này sang kiểu khác. Đây là một sự tiện lợi giúp tránh việc phải chuyển đổi giá trị một cách tường minh.
JavaScript khá “mạnh tay” khi nói đến ép kiểu. Một kiểu thường có thể bị chuyển đổi một cách âm thầm mà bạn không hề mong đợi. Hầu hết thời gian, điều này hữu ích; ví dụ:
let person = { age: 21 };
if (person.age) {
alert("người này đã được sinh ra!");
}
Trong trường hợp này, kiểu của person.age
là “number” nhưng JavaScript ép nó thành giá trị boolean khi đánh giá điều kiện của câu lệnh if
. Hầu hết mọi số, dù dương hay âm, sẽ chuyển đổi thành true
khi bị ép kiểu thành boolean. Nhưng 0
sẽ chuyển đổi thành false
, vì vậy ép kiểu là một cách tắt hữu ích để tránh phải viết if (person.age !== 0)
.
Hai giá trị số khác cũng sẽ bị ép kiểu thành false
: -0
và NaN
. NaN
là một giá trị đặc biệt viết tắt của “Not a Number” (Không phải là số) và đại diện cho một số kết quả biểu thức không hợp lệ như 42 / "hello"
. Lưu ý rằng typeof NaN
trả về ‘number’! Các kiểu khác cũng có giá trị ép kiểu thành false
, như chuỗi rỗng (""
).
Bạn có thể không ngờ tới một kiểu bị ép kiểu theo một cách nào đó. Ví dụ, [] == ""
sẽ trả về true
vì cả hai giá trị đều được ép kiểu thành boolean false
. Các quy tắc chính xác về việc giá trị nào chuyển đổi thành giá trị nào khác, trong các trường hợp khác nhau, rất dài dòng và khó nhớ. Để tránh phải nhớ, hãy sử dụng ===
bất cứ khi nào có thể.
Sử Dụng Toán Tử typeof
và Xử Lý Mảng
Nếu bạn muốn kiểm tra kiểu của một biến, bạn có thể sử dụng toán tử typeof
. Ví dụ:
function reset_value(val) {
switch (typeof val) {
case "object":
return {};
case "string":
return "";
case "number":
return 0;
}
}
Một lưu ý quan trọng là typeof null
trả về ‘object’. Đây được coi là một lỗi thiết kế của ngôn ngữ, và đã có nỗ lực để sửa lỗi này, nhưng nhu cầu tương thích ngược trên internet đã khiến việc thay đổi trở nên không thực tế. Để kiểm tra giá trị null, hãy sử dụng === null
.
Bạn có thể tự hỏi mảng (arrays) thuộc kiểu dữ liệu nào trong hệ thống kiểu của JavaScript. Chúng trông giống như một kiểu giá trị riêng biệt:
let myArray = [ "apple", "banana", "cherry" ];
Tuy nhiên, JavaScript coi một mảng là một loại của Object:
> typeof [ 1, 2, 3 ]
'object'
Điều này có thể gây bất tiện, nhưng bạn có thể kiểm tra xem một đối tượng có phải là một mảng cụ thể hay không bằng cách sử dụng phương thức static isArray
của lớp Array
:
> Array.isArray([1, 2, 3])
true
Ép Kiểu Với Các Toán Tử Toán Học
Ép kiểu là một nguồn gây ra lỗi và hiểu lầm đặc biệt lớn. Ví dụ:
> "42" + "18"
'4218'
> "42" + 18
'4218'
> 42 + "18"
'4218'
Trong mọi trường hợp, việc cố gắng cộng hai số mà một trong số đó là chuỗi sẽ dẫn đến một giá trị chuỗi kết hợp cả hai như chuỗi. Điều này là do toán tử +
được “quá tải” (overloaded) cho chuỗi để hoạt động như một toán tử nối chuỗi thay vì cộng số học.
Tuy nhiên, điều này không đúng với phép nhân:
> "42" * 2
84
> "42" * "2"
84
Đây là trường hợp ngược lại: toán tử *
sẽ ép kiểu bất kỳ giá trị chuỗi nào thành số, miễn là chúng trông giống số.
Chuyển Đổi Kiểu Tường Minh (Type Conversion)
Chuyển đổi kiểu (type conversion) tương tự như ép kiểu (type coercion), nhưng nó chuyển đổi một giá trị sang một kiểu khác một cách tường minh (có chủ đích). Một trường hợp sử dụng rất phổ biến là chuyển đổi một chuỗi thành một số:
> let pageNum = new URLSearchParams(document.location.search).get("page");
> pageNum // '1'
> parseInt(pageNum) // 1
Bạn cũng có thể xây dựng một giá trị của một kiểu nhất định bằng cách sử dụng các hàm bao bọc (wrapper functions) tích hợp sẵn cho mỗi kiểu nguyên thủy (trừ null
và undefined
): Boolean
, Number
, BigInt
, String
, và Symbol
.
> Number("42") + 18
60
Kết Luận
Việc hiểu rõ về các kiểu dữ liệu trong JavaScript không chỉ là kiến thức cơ bản mà còn là chìa khóa để viết code chất lượng, tránh những lỗi không đáng có và tối ưu hóa hiệu suất ứng dụng. Đặc biệt, việc nắm vững cơ chế ép kiểu (type coercion) và phân biệt giữa các toán tử so sánh ==
và ===
sẽ giúp bạn kiểm soát chặt chẽ hơn luồng dữ liệu và logic chương trình.
Hãy luôn ưu tiên sử dụng toán tử so sánh nghiêm ngặt (===
) bất cứ khi nào có thể để đảm bảo cả giá trị và kiểu dữ liệu đều khớp, từ đó giảm thiểu các vấn đề phát sinh từ việc ép kiểu tự động. Với những kiến thức này, bạn đã có một nền tảng vững chắc để tiếp tục khám phá và thành thạo JavaScript.
Bạn có những kinh nghiệm hay thắc mắc nào về kiểu dữ liệu JavaScript không? Hãy chia sẻ ý kiến của bạn trong phần bình luận bên dưới hoặc khám phá thêm các bài viết chuyên sâu khác về lập trình JavaScript trên thuthuatso.net!