Hiểu sâu về kiểu dữ liệu- Primitive Types (Kiểu nguyên thủy) và Reference Types (Kiểu tham chiếu)

Trong JavaScript, các kiểu dữ liệu được chia thành hai nhóm chính: primitive types (kiểu nguyên thủy)reference types (kiểu tham chiếu). Mỗi loại hoạt động khác nhau trong cách lưu trữ và xử lý dữ liệu.


1. Primitive Types (Kiểu nguyên thủy)

Định nghĩa

Kiểu nguyên thủy là những giá trị được lưu trực tiếp trong vùng nhớ (stack) và không thể thay đổi (immutable).

Các kiểu nguyên thủy trong JavaScript:

  • number: Đại diện cho các số (cả số nguyên và số thực). Ví dụ: 42, 3.14
  • string: Đại diện cho chuỗi ký tự. Ví dụ: "hello", 'world'
  • boolean: Đại diện cho giá trị đúng/sai. Ví dụ: true, false
  • undefined: Biến đã được khai báo nhưng chưa gán giá trị.
  • null: Giá trị rỗng hoặc không có giá trị nào.
  • symbol: Đại diện cho một giá trị duy nhất và bất biến.
  • bigint: Đại diện cho số nguyên lớn. Ví dụ: 123n.

Đặc điểm:

  • Immutable: Không thể thay đổi giá trị. Khi thực hiện thay đổi, nó tạo ra một giá trị mới thay vì sửa đổi giá trị ban đầu.
  • So sánh: Được so sánh bằng giá trị (value).

Ví dụ:

Ví dụ :
let a = 10;
let b = a; // Sao chép giá trị
b = 20;
 
console.log(a); // 10 (giá trị ban đầu không thay đổi)
console.log(b); // 20

Ở đây, ab lưu giữ hai bản sao riêng biệt của giá trị 10.

2. Reference Types (Kiểu tham chiếu)

Định nghĩa

Kiểu tham chiếu lưu trữ một tham chiếu (địa chỉ bộ nhớ) tới vùng nhớ (heap) nơi chứa dữ liệu thực.

Các kiểu tham chiếu trong JavaScript:

  • Object: Bao gồm Object, Array, Function, Date, RegExp, v.v.
  • Array: Mảng các giá trị.
  • Function: Các hàm.
  • Class: Các đối tượng thuộc lớp.

Đặc điểm:

  • Mutable: Có thể thay đổi giá trị trực tiếp.
  • So sánh: Được so sánh bằng tham chiếu (reference). Hai đối tượng khác nhau có thể chứa dữ liệu giống nhau nhưng không bằng nhau vì tham chiếu khác nhau.

Ví dụ:

Ví dụ :
let obj1 = { name: "John" };
let obj2 = obj1; // Sao chép tham chiếu
obj2.name = "Doe";
 
console.log(obj1.name); // "Doe" (giá trị đã thay đổi)
console.log(obj2.name); // "Doe"

Cả obj1obj2 đều tham chiếu đến cùng một đối tượng trong bộ nhớ.

Sự khác biệt chính giữa Primitive và Reference

Thuộc tínhPrimitive TypesReference Types
Lưu trữLưu trực tiếp giá trị trong stack.Lưu tham chiếu trong stack, dữ liệu trong heap.
Thay đổiKhông thể thay đổi (immutable).Có thể thay đổi (mutable).
Sao chépSao chép giá trị.Sao chép tham chiếu.
So sánhSo sánh bằng giá trị.So sánh bằng tham chiếu.

3. Một số điểm cần lưu ý

So sánh giá trị với tham chiếu

Ví dụ :
let a = 10;
let b = 10;
 
console.log(a === b); // true (so sánh giá trị)
 
let obj1 = { x: 1 };
let obj2 = { x: 1 };
 
console.log(obj1 === obj2); // false (so sánh tham chiếu)

Cloning và sao chép đối tượng

 

Để sao chép dữ liệu kiểu tham chiếu, bạn có thể sử dụng sao chép nông (shallow copy) nếu chỉ cần sao chép lớp ngoài cùng, hoặc sao chép sâu (deep copy) nếu muốn tạo một bản sao hoàn toàn độc lập với dữ liệu ban đầu, thay vì chỉ sao chép tham chiếu.

Sao chép nông (shallow copy)

Ví dụ :
let obj = { name: "Alice", age: 25 };
let clone = { ...obj }; // Sao chép bằng spread operator
 
clone.age = 30;
console.log(obj.age); // 25 (không bị thay đổi)

Sao chép sâu (deep copy)

Ví dụ :
let obj = { name: "Alice", address: { city: "NYC" } };
let deepClone = JSON.parse(JSON.stringify(obj));
 
deepClone.address.city = "LA";
console.log(obj.address.city); // "NYC" (không bị thay đổi)

NOTE:

  • Shallow Copy: Chỉ tạo bản sao độc lập ở lớp đầu tiên, nhưng vẫn giữ tham chiếu đến các đối tượng con bên trong.
  • Deep Copy: Tạo ra bản sao hoàn toàn độc lập, bao gồm cả các đối tượng con, nên phù hợp khi cần tách biệt hoàn toàn dữ liệu.
  • Trong Shallow Copy, các thuộc tính kiểu dữ liệu nguyên thủy (primitive types) được sao chép độc lập, nhưng các thuộc tính kiểu tham chiếu (reference types, như Object hoặc Array) chỉ sao chép tham chiếu, dẫn đến việc chia sẻ vùng nhớ. ->  (obj1.address.city === obj.address.city)

4. Kết luận

  • Kiểu nguyên thủy đơn giản và không thay đổi, thích hợp cho các giá trị cố định.
  • Kiểu tham chiếu mạnh mẽ và linh hoạt, thích hợp cho các cấu trúc dữ liệu phức tạp.
    Nắm vững sự khác biệt sẽ giúp bạn tránh các lỗi liên quan đến mutabilityreference sharing khi lập trình.