Bảy kiểu nguyên thủy và mọi thứ còn lại
JavaScript chia các giá trị thành hai nhóm. Một bên là bảy kiểu nguyên thủy (primitive): những giá trị đơn giản, bất biến. Bên kia là đối tượng: mọi thứ phức hợp, có thể thay đổi hoặc có thể gọi được. Đó là toàn bộ hệ thống kiểu ở cấp giá trị.
Bảy kiểu nguyên thủy:
Bất cứ thứ gì không có trong danh sách trên, như mảng, hàm, ngày tháng, biểu thức chính quy, {} thông thường, đều là đối tượng. typeof cho bạn biết kiểu khi chạy, và bạn sẽ để ý thấy điểm lạ nổi tiếng ở dòng cuối. typeof null đã trả về 'object' từ năm 1995 và sẽ không bao giờ được sửa; có quá nhiều code hiện có dựa vào nó.
Kiểu nguyên thủy là một giá trị, không phải vật chứa
Cách nghĩ hữu ích nhất: một giá trị nguyên thủy chính là giá trị của nó. Số 3 không phải là một chiếc hộp chứa số 3, nó chỉ là 3. Hai biến cùng giữ 3 là đang giữ cùng một giá trị, không phải hai bản sao cùng trỏ tới một thứ dùng chung:
Kiểu nguyên thủy được so sánh theo giá trị. Đối tượng được so sánh theo tham chiếu. Chỉ một khác biệt đó là nguyên nhân của rất nhiều khoảnh khắc "sao cái này lại là false?" về sau, đặc biệt khi so sánh mảng hoặc đối tượng bằng ===.
Kiểu nguyên thủy là bất biến
Bạn không thể sửa đổi một giá trị nguyên thủy. Mọi thao tác trông giống như sửa đổi thực ra đều tạo ra một giá trị mới:
Lời gọi đầu tiên tạo ra một chuỗi mới rồi bỏ đi, vì không có gì nhận giá trị trả về. Lời gọi thứ hai gán lại name. Chuỗi "ada" ban đầu không hề bị thay đổi, và cũng không thể bị thay đổi. Với số cũng vậy: x + 1 tạo ra một số mới, không sửa đổi x.
Đó là lý do const với chuỗi hoặc số thực sự an toàn. Giá trị không thể thay đổi, và const ngăn bạn gán lại biến.
Number, BigInt và vì sao có tới hai kiểu
number của JavaScript là số thực 64 bit. Điều đó nghĩa là tính toán nhanh nhưng có giới hạn: số nguyên chỉ chính xác tới Number.MAX_SAFE_INTEGER (2^53 - 1):
Vượt qua ngưỡng đó, các số nguyên bắt đầu trùng nhau. bigint tồn tại cho những số nguyên cần giữ chính xác ở bất kỳ kích thước nào. Bạn viết một bigint bằng cách thêm hậu tố n:
bigint và number không thể trộn trong phép tính, vì như vậy sẽ mất đi ý nghĩa của độ chính xác thêm. Hãy dùng bigint khi làm việc với ID trong cơ sở dữ liệu, mốc thời gian tính bằng nano giây hoặc mật mã học. Phép tính thông thường cứ dùng number.
Chuỗi cũng là kiểu nguyên thủy
Một chuỗi trong JavaScript là kiểu nguyên thủy, không phải đối tượng, dù nó có các phương thức và thuộc tính như .length, .slice, .toUpperCase:
Bên dưới, khi bạn gọi một phương thức trên chuỗi, JavaScript tạm thời bọc nó trong một đối tượng String để lời gọi phương thức hoạt động, rồi bỏ lớp bọc đó đi. Bạn không cần nghĩ tới lớp bọc này: chỉ cần biết rằng chuỗi hoạt động như giá trị (bất biến, so sánh theo giá trị) dù có rất nhiều phương thức.
Dấu nháy đơn, nháy kép và dấu backtick đều tạo ra cùng một kiểu. Backtick còn cho phép chèn biến và viết chuỗi nhiều dòng, nội dung của bài tài liệu tiếp theo.
null và undefined
Hai kiểu nguyên thủy mang nghĩa "không có giá trị", và chúng không thể thay thế cho nhau.
undefined là thứ bạn nhận được khi một thứ chưa bao giờ được gán: biến đã khai báo nhưng chưa gán, đối số hàm bị thiếu, thuộc tính không tồn tại:
null là thứ bạn viết khi muốn nói "cố ý để trống":
Quy ước đại khái: undefined là cách ngôn ngữ nói "ở đây không có gì", còn null là cách lập trình viên nói điều đó. Cả hai đều là falsy, cả hai đều không bằng các giá trị thông thường, và cả hai đều có trang riêng ở phần sau.
Symbol: duy nhất ngay từ khi tạo ra
symbol là kiểu nguyên thủy ít được dùng nhất. Mỗi symbol bạn tạo ra đều là duy nhất, kể cả khi hai symbol được tạo từ cùng một mô tả:
Symbol hữu ích khi làm khóa của đối tượng mà không thể trùng với các khóa sẵn có: một thư viện có thể gắn siêu dữ liệu vào đối tượng của bạn bằng symbol và chắc chắn rằng không đoạn code nào khác ghi đè lên nó. Bạn sẽ gặp lại symbol khi học về iterator và các symbol có sẵn như Symbol.iterator.
Kiểm tra kiểu khi chạy
typeof xử lý được phần lớn trường hợp. Hãy nhớ những điểm lạ sau:
Với null, hãy so sánh trực tiếp: value === null. Với mảng, dùng Array.isArray(value). Để hỏi "đây có phải kiểu nguyên thủy nào đó không?", không có hàm có sẵn nào, nhưng cách viết sau đủ rõ ràng:
Kiểu nguyên thủy và đối tượng: cái bẫy khi gán
Thêm một điều nên xem trước khi đi tiếp. Vì kiểu nguyên thủy là giá trị còn đối tượng là tham chiếu, phép gán hoạt động khác nhau:
Với kiểu nguyên thủy, b = a sao chép giá trị. Với đối tượng, y = x sao chép tham chiếu: cả hai tên cùng trỏ tới một đối tượng bên dưới. Sửa đổi qua tên này sẽ ảnh hưởng tới tên kia. Đây là nguồn gốc lớn nhất của những bug kiểu "khoan, sao cái đó lại đổi?" trong JavaScript.
Những điều cần nhớ
- Bảy kiểu nguyên thủy:
string,number,bigint,boolean,null,undefined,symbol. Mọi thứ khác đều là đối tượng. - Kiểu nguyên thủy là bất biến và so sánh theo giá trị; đối tượng có thể thay đổi và so sánh theo tham chiếu.
typeofcho biết kiểu khi chạy, với hai điểm lạ nên ghi nhớ:typeof null === "object"vàtypeof function === "function".numberlà số thực 64 bit với ngưỡng an toàn cho số nguyên;biginttồn tại cho các số nguyên chính xác vượt quá ngưỡng đó.
Tiếp theo: chuỗi và template literal
Chuỗi là kiểu nguyên thủy bạn dùng nhiều nhất, và template literal (những chuỗi dùng dấu backtick) giúp tạo chuỗi thật dễ dàng: chèn biến, văn bản nhiều dòng, tagged template. Đó là nội dung của trang tiếp theo.
Câu hỏi thường gặp
JavaScript có bao nhiêu kiểu dữ liệu nguyên thủy?
Bảy kiểu: string, number, bigint, boolean, null, undefined và symbol. Mọi thứ khác, như mảng, hàm, ngày tháng, đối tượng thông thường, đều là đối tượng. Bạn có thể kiểm tra kiểu của một giá trị khi chạy bằng typeof, với một điểm lạ có từ lâu là typeof null trả về 'object'.
Kiểu nguyên thủy và đối tượng trong JavaScript khác nhau thế nào?
Kiểu nguyên thủy là các giá trị bất biến và được so sánh theo giá trị: hai số 3 là cùng một số 3. Đối tượng có thể thay đổi và được so sánh theo tham chiếu: hai {} là khác nhau dù trông giống hệt nhau. Gán một giá trị nguyên thủy sẽ sao chép giá trị; gán một đối tượng sẽ sao chép tham chiếu tới cùng một dữ liệu bên dưới.
Kiểu nguyên thủy trong JavaScript có thực sự bất biến không?
Có. Bạn không thể sửa trực tiếp một giá trị nguyên thủy: 'hello'.toUpperCase() trả về một chuỗi mới, không thay đổi chuỗi gốc. Gán lại một biến (x = x + 1) chỉ đổi giá trị của biến sang một giá trị nguyên thủy khác; giá trị ban đầu không bao giờ bị thay đổi. Đó là lý do const name = 'Ada' vẫn cho phép bạn tạo chuỗi mới từ name.
Vì sao typeof null trả về 'object'?
Đây là một bug trong bản cài đặt đầu tiên năm 1995 và chưa bao giờ được sửa vì có quá nhiều code phụ thuộc vào hành vi này. Để kiểm tra null, hãy so sánh bằng ===: value === null. Để kiểm tra undefined, dùng value === undefined hoặc typeof value === 'undefined'.